Load libraries

library(signal)
G3;
Adjuntando el paquete: ‘signal’

gG3;The following objects are masked from ‘package:stats’:

    filter, poly

g
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks signal::filter(), stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(here)
G3;here() starts at C:/Users/jimenezalfaro/OneDrive - Universidad de Oviedo/IMIB/Analyses/MOTIVATE/MOTIVATE_validation
g
library(lubridate)
library(dtplyr)
G3;Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
g
library(sf)
G3;Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
g
library(knitr)
library(mgcv)
G3;Cargando paquete requerido: nlme
gG3;
Adjuntando el paquete: ‘nlme’

gG3;The following object is masked from ‘package:dplyr’:

    collapse

gG3;This is mgcv 1.9-1. For overview type 'help("mgcv-package")'.
g
library(future)
library(furrr)
library(progressr)
library(pracma)
G3;
Adjuntando el paquete: ‘pracma’

gG3;The following object is masked from ‘package:mgcv’:

    magic

gG3;The following object is masked from ‘package:purrr’:

    cross

gG3;The following objects are masked from ‘package:signal’:

    conv, ifft, interp1, pchip, polyval, roots

g

Set a simple console progress bar

handlers("txtprogressbar") # Simple console progress bar

Supress package messages

suppressPackageStartupMessages({
  library(mgcv)
  library(nlme)
})

Load previously created objects

load(file = "objects/data_RS_S2_bands_indices.Rdata")
load(file = "objects/GAM_data_S2.Rdata")
load(file = "objects/ts_plots_q1_S2.Rdata")
G1;Error en readChar(con, 5L, useBytes = TRUE): 
  no se puede abrir la conexión
Error durante el wrapup: no hay tantas estructuras en la pila
H1;Errorh: no more error handlers available (recursive errors?); invoking 'abort' restart
g
load(file = "objects/ts_plots_q0_S2.Rdata")
G1;Error en readChar(con, 5L, useBytes = TRUE): 
  no se puede abrir la conexión
gG1;Error durante el wrapup: no hay tantas estructuras en la pila
H1;Errorh: no more error handlers available (recursive errors?); invoking 'abort' restart
g
load(file = "objects/smoothed_data_S2.Rdata")

Load Resurvey db

db_Europa_allobs <- read_csv(
  here("data", "clean", "db_Europa_allobs.csv")) %>%
  select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
         EUNISa_2, EUNISa_2_descr) %>%
  mutate(PlotObservationID = factor(PlotObservationID),
         EUNISa_1 = factor(EUNISa_1), EUNISa_2 = factor(EUNISa_2))
Rows: 188477 Columns: 47
── Column specification ────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (18): RS_CODE, ReSurvey site, ReSurvey plot, EUNISa, EUNISb, EUNISa_1, EUNISa_2...
dbl   (6): PlotObservationID, Lon_updated, Lat_updated, year, obs_unique_id, PLOT
lgl  (22): EUNISc, EUNISd, EUNISa_4, EUNISb_4, EUNISc_1, EUNISc_2, EUNISc_3, EUNISc_...
date  (1): date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Read files with band data

I got these files using the GEE code prepared by Bea.

These files contain all observations in the ReSurvey database from 2016 onward. In order to avoid computation problems in GEE, biogeographical units that contain more than 4500 points have been subdivided in ArcGIS.

# Set the folder path
folder_path <- "C:/Data/MOTIVATE/MOTIVATE_RS_data/S2/Bands/all"

# List CSV files
csv_files <- list.files(folder_path, full.names = TRUE, recursive = TRUE)

# Function to extract biogeo and unit from the filename
extract_info <- function(filename) {
  first_word <- strsplit(filename, "_")[[1]][1]
  biogeo <- str_extract(first_word,
                        "^(ALP|ANA|ARC|ATL|BLACKSEA|BOR|CON|MACARONESIA|MED|PANONIA|STEPPIC)")
  unit <- str_remove(first_word, biogeo)
  if (is.na(unit) || unit == "") unit <- NA_character_
  list(biogeo = biogeo, unit = unit)
  }


# Define column types: force RSrvypl to character, others auto-detected
custom_col_types <- cols(
  RSrvypl = col_character(),
  RSrvyst = col_character(),
  default = col_guess()
)

# Read and process each file
data_list <- lapply(csv_files, function(file) {
  info <- extract_info(basename(file)) # Use only the filename
  
  # Read the file
  df <- read_csv(file, col_types = custom_col_types) %>%
    # Remove columns that give column type problems when combining data
    select(-starts_with("EUNIS"), -starts_with("ReSurvey")) %>%
    mutate(biogeo = info$biogeo, unit = info$unit)
  
  return(df)
  })

# Combine all data
data_RS_S2_bands <- bind_rows(data_list) %>%
  rename(PlotObservationID = PltObID)

# View the resulting tibble
print(data_RS_S2_bands)

# Counts per biogeo and unit
print(data_RS_S2_bands %>% count(biogeo, unit), n = 100)

Some checks

Check that the year in the date of the images is not different to the sampling year:

data_RS_S2_bands %>% dplyr::filter(year != year(date))

Check how many different images are for each observation, date and time:

data_RS_S2_bands %>% group_by(PlotObservationID, date, time_utc) %>%
  summarise(n_images = n_distinct(image_id), .groups = "drop") %>%
  count(n_images)

Average the bands

When there is more than one image for each point and day, average the values of the bands:

Calculate indices

Save:

Plot n_daytime:

data_RS_S2_bands_indices %>%
  group_by(PlotObservationID) %>%
  summarise(n_days = first(n_days)) %>% ungroup() %>%
  ggplot(aes(x = n_days)) + geom_histogram(color = "black", fill = "white") +
  theme_minimal()

Compute phenological metrics from models fitted to time series data

Function

Using GAMs, reweighting and 3 iterations.

Using both a change detection method (maximum slope) and a threshold method (50% amplitude) to calculate sos and eos.

Approach similar to https://doi.org/10.1016/j.jag.2020.102172 for GAM fitting and change detection method, and to https://www.mdpi.com/2072-4292/12/22/3738 fot threshold method.

Define function to compute phenology metrics using GAM fit and NDVI / EVI / SAVI:

compute_metrics_models <- function(df, index_cols = c("NDVI", "EVI", "SAVI")) {
  suppressPackageStartupMessages({
    library(mgcv)
    library(nlme)
    })
  
  plan(multisession)  # Set up parallel processing
  
  # Create a list of index-specific data frames
  index_dfs <- lapply(index_cols, function(index_col) {
    list(index_col = index_col, df = df %>%
           select(DOY, PlotObservationID, all_of(index_col)))
    })
  
  # Define the processing function for each index
  process_index <- function(index_data) {
    index_col <- index_data$index_col
    df_index <- index_data$df %>%
      filter(!is.na(.data[[index_col]])) %>%
      arrange(DOY)
    
    plot_id <- unique(df_index$PlotObservationID)

    if (nrow(df_index) < 10) {
      message("  Skipped: insufficient data (< 10 rows)")
      return(tibble(PlotObservationID = plot_id, index = index_col,
                    sos_slope = NA_real_, sos_threshold = NA_real_,
                    pos = NA_real_, eos_slope = NA_real_, 
                    eos_threshold = NA_real_, auc_slope = NA_real_,
                    auc_threshold = NA_real_, Vmin = NA_real_, Vmax = NA_real_,
                    u = NA_real_, DOY = df_index$DOY, value = NA_real_))
    }
    
    # Replace early/late DOY values
    base_value_early <- mean(df_index %>% filter(DOY <= 50) %>% 
                               pull(index_col), na.rm = TRUE)
    base_value_late  <- mean(df_index %>% filter(DOY >= 315) %>% 
                               pull(index_col), na.rm = TRUE)

    df_index <- df_index %>%
      mutate(!!index_col := case_when(
        DOY <= 50 ~ base_value_early,
        DOY >= 315 ~ base_value_late,
        TRUE ~ .data[[index_col]]
      ))

    x <- df_index$DOY
    y <- df_index[[index_col]]
    weights <- rep(1, length(y))
    
    # GAM fit
    pred <- NULL
    for (i in 1:3) {
      gam_fit <- tryCatch({
        mgcv::bam(y ~ s(x, bs = "tp"),weights = weights)
        }, error = function(e) {
          message("  GAM fitting failed for ", plot_id, " - ", index_col, ": ", 
                  e$message)
          return(NULL)
          })
      if (is.null(gam_fit)) {
        return(tibble(
          PlotObservationID = plot_id,
          index = index_col,
          sos_slope = NA_real_,
          sos_threshold = NA_real_,
          pos = NA_real_,
          eos_slope = NA_real_, 
          eos_threshold = NA_real_, 
          auc_slope = NA_real_,
          auc_threshold = NA_real_, 
          Vmin_pre = NA_real_, 
          Vmin_post = NA_real_,
          Vmax = NA_real_, 
          u_sos = NA_real_, 
          u_eos = NA_real_,
          DOY = df_index$DOY,
          value = NA_real_))
        }
      
      pred <- tryCatch({
        predict(gam_fit, newdata = tibble(x = x))
        }, error = function(e) {
          message("Prediction failed for ", plot_id, " - ", index_col, ": ",
                  e$message)
          return(rep(NA_real_, length(x)))
          })
      
      idx_between <- which(x > 50 & x < 315 & !is.na(pred) & pred != 0)
      weights <- rep(1, length(y))
      weights[idx_between] <- (y[idx_between] / (pred[idx_between] + 1e-6))^4
      weights[weights > 1 | is.na(weights)] <- 1
      }
    
    # Compute metrics
    slope <- c(NA, diff(pred))
    idx <- which(x >= 50 & x <= 315)
    pos <- if (length(idx) > 0) x[idx][which.max(pred[idx])] else NA_real_

    sos_slope <- if (!is.na(pos)) {
      idx <- which(x < pos)
      if (length(idx) > 0) x[idx][which.max(slope[idx])] else NA_real_
    } else NA_real_

    eos_slope <- if (!is.na(pos)) {
      idx <- which(x > pos)
      if (length(idx) > 0) x[idx][which.min(slope[idx])] else NA_real_
    } else NA_real_

    integration_idx_slope <- which(x >= sos_slope & x <= 
                                     eos_slope & !is.na(pred))
    auc_slope <- if (length(integration_idx_slope) > 1) {
      sum(diff(x[integration_idx_slope]) * 
            zoo::rollmean(pred[integration_idx_slope], 2))
      } else NA_real_
    
    # Vmin antes y después del pico
    Vmin_pre <- if (!is.na(pos)) min(pred[x <= pos], na.rm = TRUE)else NA_real_
    Vmin_post <- if (!is.na(pos)) min(pred[x >= pos], na.rm = TRUE) else NA_real_
    Vmax <- max(pred, na.rm = TRUE)
    
    # Umbrales relativos
    p <- 0.5
    u_sos <- if (!is.na(Vmin_pre)) Vmin_pre + p * (Vmax - Vmin_pre) else NA_real_
    u_eos <- if (!is.na(Vmin_post)) Vmin_post + p * (Vmax - Vmin_post) else NA_real_
    
    # DOY donde se cruzan los umbrales
    sos_threshold <- if (!is.na(u_sos)) x[which(pred >= u_sos)[1]] else NA_real_
    eos_threshold <- if (!is.na(u_eos)) x[rev(which(pred >= u_eos))[1]] else NA_real_
    
    integration_idx_threshold <- which(x >= sos_threshold & 
                                         x <= eos_threshold & !is.na(pred))
    auc_threshold <- if (length(integration_idx_threshold) > 1) {
      sum(diff(x[integration_idx_threshold]) * 
            zoo::rollmean(pred[integration_idx_threshold], 2))
      } else NA_real_
    
    # 1. Predicciones por DOY
    fits_df <- tibble(
      PlotObservationID = unique(df_index$PlotObservationID),
      DOY = x,
      value = pred,
      index = index_col
      )
    
    # 2. Métricas resumen
    metrics_df <- tibble(
      PlotObservationID = unique(df_index$PlotObservationID),
      index = index_col,
      sos_slope = sos_slope,
      sos_threshold = sos_threshold,
      pos = pos,
      eos_slope = eos_slope,
      eos_threshold = eos_threshold,
      auc_slope = auc_slope,
      auc_threshold = auc_threshold,
      Vmin_pre = Vmin_pre,
      Vmin_post = Vmin_post,
      Vmax = Vmax,
      u_sos = u_sos,
      u_eos = u_eos
      )
    
    # 3. Unir por PlotObservationID, index
    final_df <- left_join(fits_df, metrics_df, 
                          by = c("PlotObservationID", "index"))
  }
  
  # Run in parallel
  results <- future_map(index_dfs, process_index, .progress = TRUE)
  results <- purrr::compact(results)  # removes NULLs
  if (length(results) == 0) return(tibble())  # or return(NULL)
  bind_rows(results)
}
# # OLD
# compute_metrics_models <- function(df, index_cols = c("NDVI", "EVI", "SAVI"), 
#                                    max_iter = 3) {
#   plan(multisession)  # Set up parallel processing
# 
#   # Create a list of index-specific data frames
#   index_dfs <- lapply(index_cols, function(index_col) {
#     list(index_col = index_col, df = df %>% 
#            
#            select(DOY, PlotObservationID, all_of(index_col)))
#   })
# 
#   # Define the processing function for each index
#   process_index <- function(index_data) {
#     index_col <- index_data$index_col
#     df_index <- index_data$df %>%
#       filter(!is.na(.data[[index_col]])) %>%
#       arrange(DOY)
# 
#     # if (nrow(df_index) < 11) return(NULL)
# 
#     # Replace early/late DOY values
#     base_value_early <- mean(df_index %>% filter(DOY <= 50) %>% 
#                                pull(index_col), na.rm = TRUE)
#     base_value_late  <- mean(df_index %>% filter(DOY >= 315) %>% 
#                                pull(index_col), na.rm = TRUE)
# 
#     df_index <- df_index %>%
#       mutate(!!index_col := case_when(
#         DOY <= 50 ~ base_value_early,
#         DOY >= 315 ~ base_value_late,
#         TRUE ~ .data[[index_col]]
#       ))
# 
#     x <- df_index$DOY
#     y <- df_index[[index_col]]
#     fits_df <- tibble(DOY = x, observed = y)
# 
#     # Initial unweighted GAM
#     gam_unweighted <- mgcv::bam(y ~ s(x, bs = "tp"))
#     fits_df$unweighted <- predict(gam_unweighted, newdata = tibble(x = x))
# 
#     # Iterative reweighting
#     weights <- rep(1, length(y))
#     for (i in 1:max_iter) {
#       gam_fit <- mgcv::bam(y ~ s(x, bs = "tp"), weights = weights)
#       pred <- predict(gam_fit, newdata = tibble(x = x))
#       fits_df[[paste0("iter_", i)]] <- pred
# 
#       idx_between <- which(x > 50 & x < 315 & !is.na(pred) & pred != 0)
#       weights <- rep(1, length(y))
#       weights[idx_between] <- (y[idx_between] / (pred[idx_between] + 1e-6))^4
#       weights[weights > 1 | is.na(weights)] <- 1
#     }
# 
#     # Compute metrics
#     metrics_list <- list()
#     fit_types <- c("unweighted", paste0("iter_", 1:max_iter))
#     for (fit_type in fit_types) {
#       pred <- fits_df[[fit_type]]
#       slope <- c(NA, diff(pred))
#       idx <- which(x >= 50 & x <= 315)
#       pos <- if (length(idx) > 0) x[idx][which.max(pred[idx])] else NA_real_
# 
#       sos_slope <- if (!is.na(pos)) {
#         idx <- which(x < pos)
#         if (length(idx) > 0) x[idx][which.max(slope[idx])] else NA_real_
#       } else NA_real_
# 
#       eos_slope <- if (!is.na(pos)) {
#         idx <- which(x > pos)
#         if (length(idx) > 0) x[idx][which.min(slope[idx])] else NA_real_
#       } else NA_real_
# 
#       integration_idx_slope <- 
#         which(x >= sos_slope & x <= eos_slope & !is.na(pred))
#       auc_slope <- if (length(integration_idx_slope) > 1) {
#         sum(diff(x[integration_idx_slope]) * 
#               zoo::rollmean(pred[integration_idx_slope], 2))
#         } else NA_real_
#       
#       # Dynamic threshold method
#       Vmin <- min(pred, na.rm = TRUE)
#       Vmax <- max(pred, na.rm = TRUE)
#       p <- 0.5
#       u <- Vmin + p * (Vmax - Vmin)
#       sos_threshold <- x[which(pred >= u)[1]]
#       eos_threshold <- x[rev(which(pred >= u))[1]]
#       integration_idx_threshold <- 
#         which(x >= sos_threshold & x <= eos_threshold & !is.na(pred))
#       auc_threshold <- if (length(integration_idx_threshold) > 1) {
#         sum(diff(x[integration_idx_threshold]) * 
#               zoo::rollmean(pred[integration_idx_threshold], 2))
#         } else NA_real_
#       
#       metrics_list[[fit_type]] <- tibble(
#         PlotObservationID = unique(df_index$PlotObservationID),
#         index = index_col,
#         fit_type = fit_type,
#         sos_slope = sos_slope,
#         sos_threshold = sos_threshold,
#         pos = pos,
#         eos_slope = eos_slope,
#         eos_threshold = eos_threshold,
#         auc_slope = auc_slope,
#         auc_threshold = auc_threshold,
#         Vmin = Vmin,
#         Vmax = Vmax,
#         u = u
#       )
#     }
# 
#     fits_long <- fits_df %>%
#       pivot_longer(cols = -DOY, names_to = "fit_type", values_to = "value") %>%
#       mutate(PlotObservationID = unique(df_index$PlotObservationID), 
#              index = index_col)
# 
#     list(metrics = bind_rows(metrics_list), fits = fits_long)
#   }
# 
#   # Run in parallel
#   results <- future_map(index_dfs, process_index, .progress = TRUE)
# 
#   # Combine results
#   metrics_df <- bind_rows(map(results, "metrics"))
#   fits_df <- bind_rows(map(results, "fits"))
# 
#   GAM_data <- fits_df %>%
#     left_join(metrics_df, by = c("PlotObservationID", "index", "fit_type")) %>%
#     mutate(method = "GAM") %>%
#     select(PlotObservationID, DOY, method, fit_type, index, value, sos_slope,
#            sos_threshold, pos, eos_slope, eos_threshold, auc_slope, 
#            auc_threshold, Vmin, Vmax, u)
# 
#   return(GAM_data)
# }
# OLD
# compute_metrics_models <- function(
#   # Data frame df with index values over time (DOY)
#   df, 
#   # Name of the vegetation indices columns (e.g., "NDVI", "EVI", "SAVI")
#   index_cols = c("NDVI", "EVI", "SAVI"),
#   # Number of iterations for the reweighting process to refine the GAM fit
#   max_iter = 3
# ) {
#   # Initialize lists to store results
#   metrics_list <- list()
#   fits_list <- list()
#   
#   # Loop over each index column
#   for (index_col in index_cols) {
#     # Remove rows with missing index values and sort data by DOY
#     df_index <- df %>%
#       dplyr::filter(!is.na(.data[[index_col]])) %>%
#       arrange(DOY)
#     
#     # Replace values in DOY 1–50 and DOY 315–end with separate base values
#     base_value_early <- mean(df_index %>%
#                                dplyr::filter(DOY >= 1 & DOY <= 50) %>%
#                                pull(index_col), na.rm = TRUE)
#     base_value_late  <- mean(df_index %>%
#                                dplyr::filter(DOY >= 315) %>%
#                                pull(index_col), na.rm = TRUE)
#     
#     df_index <- df_index %>%
#       mutate(!!index_col := case_when(
#         DOY <= 50 ~ base_value_early,
#         DOY >= 315 ~ base_value_late,
#         TRUE ~ .data[[index_col]]
#       ))
#     
#     # Extract x (DOY) and y (index) vectors for modelling
#     x <- df_index$DOY
#     y <- df_index[[index_col]]
#     
#     # If there are fewer than 11 observations or all values are NA, skip
#     # VERIFY if this value is OK!
#     if (length(x) < 11 || all(is.na(y))) {
#       next
#     }
#     
#     # Create tibble to store original and predicted index values
#     fits_df <- tibble(DOY = x, observed = y)
#     
#     # Fit initial GAM (unweighted):
#     # Fit a GAM with a thin plate spline (bs = "tp") to smooth the index curve
#     gam_unweighted <- mgcv::bam(y ~ s(x, bs = "tp"))
#     # Store the predicted values in the unweighted column
#     fits_df$unweighted <- predict(gam_unweighted, newdata = tibble(x = x))
#     
#     # Iterative reweighted GAM fitting
#     weights <- rep(1, length(y)) # Start with equal weights
#     for (i in 1:max_iter) {
#       # Update prediction and recalculate weights to emphasize
#       # points where observed index is higher than predicted
#       gam_fit <- mgcv::bam(y ~ s(x, bs = "tp"), weights = weights)
#       pred <- predict(gam_fit, newdata = tibble(x = x))
#       if (any(is.na(pred))) {
#         print(paste("Warning: NA predictions in iteration", i, "for", index_col))
#       }
#       fits_df[[paste0("iter_", i)]] <- pred
#       
#       # Apply weighting only between DOY 50 and 315
#       weights <- rep(1, length(y))
#       idx_between <- which(x > 50 & x < 315 & !is.na(pred) & pred != 0)
#       weights[idx_between] <- (y[idx_between] / pred[idx_between])^4
#       weights[weights > 1] <- 1
#       weights[is.na(weights)] <- 1
#     }
#     
#     # Compute phenological metrics for each fit_type
#     fit_types <- c("unweighted", paste0("iter_", 1:max_iter))
#     for (fit_type in fit_types) {
#       pred <- fits_df[[fit_type]]
#       
#       if (all(is.na(pred))) {
#         print(paste("All predictions are NA for", fit_type, "on", index_col))
#       }
#       
#       slope <- c(NA, diff(pred))
#       
#       pos <- {
#         idx <- which(x >= 50 & x <= 315)
#         if (length(idx) > 0) x[idx][which.max(pred[idx])] else NA_real_
#       }
#       
#       if (is.na(pos)) {
#         print(paste("POS is NA for", fit_type, "on", index_col))
#       }
#       
#       sos <- {
#         idx <- which(x < pos)
#         if (length(idx) > 0) {
#           sub_x <- x[idx]
#           sub_slope <- slope[idx]
#           sub_x[which.max(sub_slope)]
#         } else NA_real_
#       }
#       
#       eos <- {
#         idx <- which(x > pos)
#         if (length(idx) > 0) {
#           sub_x <- x[idx]
#           sub_slope <- slope[idx]
#           sub_x[which.min(sub_slope)]
#         } else NA_real_
#       }
#       
#       # Compute time-integrated index (AUC) between SOS and EOS
#       integration_idx <- which(x >= sos & x <= eos & !is.na(pred))
#       if (length(integration_idx) > 1) {
#         auc <- sum(diff(x[integration_idx]) * 
#                      zoo::rollmean(pred[integration_idx], 2))
#       } else {
#         auc <- NA_real_
#       }
#       
#       metrics_list[[paste(index_col, fit_type, sep = "_")]] <- tibble(
#         PlotObservationID = unique(df$PlotObservationID),
#         index = index_col,
#         fit_type = fit_type,
#         sos = sos,
#         pos = pos,
#         eos = eos,
#         auc = auc
#       )
#     }
#     
#     # Store fits in long format
#     fits_long <- fits_df %>%
#       pivot_longer(cols = -DOY, names_to = "fit_type", values_to = "value") %>%
#       mutate(
#         PlotObservationID = unique(df$PlotObservationID),
#         index = index_col
#       )
#     
#     fits_list[[index_col]] <- fits_long
#   }
#   
#   # Fallback in case no metrics were computed
#   if (length(metrics_list) == 0) {
#     print(paste("No metrics computed for PlotObservationID:",
#                 unique(df$PlotObservationID)))
#     return(tibble())
#   }
#   
#   # Combine metrics and fits into a single GAM_data tibble
#   metrics_df <- bind_rows(metrics_list[!sapply(metrics_list, is.null)])
#   fits_df <- bind_rows(fits_list)
#   
#   GAM_data <- fits_df %>%
#     left_join(metrics_df, by = c("PlotObservationID", "index", "fit_type")) %>%
#     mutate(method = "GAM") %>%
#     # Specify column order
#     select(PlotObservationID, DOY, method, fit_type, index, value,
#            sos, pos, eos, auc)
#   
#   # Return the combined tibble
#   return(GAM_data)
# }

Calculation

Apply the function with batch processing

plan(multisession, workers = availableCores() - 1)

ids <- unique(data_RS_S2_bands_indices$PlotObservationID)
batches <- split(ids, ceiling(seq_along(ids) / 50))  # batches of 50

start_total <- Sys.time()

GAM_data <- map_dfr(seq_along(batches), function(i) {
  batch_ids <- batches[[i]]
  total_batches <- length(batches)
  batch_file <- file.path("objects/GAM_batches", paste0("batch_", i, ".rds"))

  if (file.exists(batch_file)) {
    message("✅ Batch  ", i, " of ", total_batches, 
            " already processed. Loading from file.")
    return(readRDS(batch_file))
  }

  message("🔄 Processing batch  ", i, " of ", total_batches, " with ",
          length(batch_ids), " IDs...")

  start_batch <- Sys.time()

  result <- data_RS_S2_bands_indices %>%
    filter(PlotObservationID %in% batch_ids) %>%
    group_split(PlotObservationID) %>%
    set_names(map_chr(., ~ as.character(unique(.x$PlotObservationID)))) %>%
    future_map_dfr(~ compute_metrics_models(df = .,
                                            index_cols = c("NDVI", "EVI", "SAVI")),
                   .progress = TRUE)

  end_batch <- Sys.time()
  duration <- round(difftime(end_batch, start_batch, units = "mins"), 2)
  message("⏱️ Batch time ", i, ": ", duration, " minutes")

  message("💾 Saving batch ", i, " to file...")
  saveRDS(result, batch_file)
  message("✅ Batch ", i, " saved.") 

  result
})

end_total <- Sys.time()
total_time <- round(difftime(end_total, start_total, units = "mins"), 2)
message("⏱️ Total time: ", total_time, " minutes")
plan(sequential)

Save

Look:

GAM_data

Save as an object:

Extract average values of indices per month

extract_monthly_avg_indices <- function(
    GAM_data, 
    monthly_doys = list("01" = 1:31, "02" = 32:59, "03" = 60:90, "04" = 91:120, 
                        "05" = 121:151, "06" = 152:181, "07" = 182:212, 
                        "08" = 213:243, "09" = 244:273, "10" = 274:304,
                        "11" = 305:334, "12" = 335:365)) {
  GAM_data %>%
    mutate(month = purrr::map_chr(DOY, function(doy) {
      month_name <- names(monthly_doys)[sapply(monthly_doys, 
                                               function(r) doy %in% r)]
      if (length(month_name) > 0) month_name else NA_character_
    })) %>%
    filter(!is.na(month)) %>%
    group_by(PlotObservationID, index, month) %>%
    summarise(avg_value = mean(value, na.rm = TRUE), .groups = "drop") %>%
    mutate(avg_value = ifelse(is.infinite(avg_value), NA, avg_value)) %>%
    arrange(PlotObservationID, match(month, names(monthly_doys))) %>%
    pivot_wider(names_from = month, values_from = avg_value,
                names_prefix = "avg_value_")
}
monthly_avg_indices <- extract_monthly_avg_indices(GAM_data)
monthly_avg_indices

Save as an object:

Assess time series quality

For the time series to be acceptable, it should have a reasonable number of time points, and these points should be distributed along almost all months (could be ok to miss the winter months).

In GAM data, check how many time points are there for each PlotObservationID, how many months, and which months are missing.

ts_quality <- GAM_data %>%
  # Filter only NDVI (all indices will have the same time points)
  dplyr::filter(index == "NDVI") %>%
  # Get month from DOY
  mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
  # For each PlotObservationID
  group_by(PlotObservationID) %>%
  # Get the number of time points (days) and the number of months
  summarise(
    n_days = n_distinct(DOY),
    n_months = n_distinct(month),
    .groups = "drop"
  ) %>%
  left_join(GAM_data %>%
              # Filter only NDVI
              dplyr::filter(index == "NDVI") %>%
              # Get month from DOY
              mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
              # Get unique values of PlotObservationID and month
              distinct(PlotObservationID, month) %>%
              # Add 1 as value
              mutate(value = 1) %>%
              # Reshape to wide format and add zeros when month is missing
              pivot_wider(
                names_from = month,
                names_prefix = "month",
                values_from = value,
                values_fill = 0),
            by = "PlotObservationID")

Histograms time points and n months:

ggplot(ts_quality, aes(x = n_days)) +
  geom_histogram(color = "black", fill = "white") +
  xlab("Number of time points (days) in the S2 time series") +
  theme_minimal()

ggplot(ts_quality, aes(x = n_months)) +
  geom_histogram(color = "black", fill = "white") +
  xlab("Number of months in the S2 time series") +
  theme_minimal()

Count how many PlotObservationIDs have missing data (value 0) for each month:

obs_missing_month <- ts_quality %>%
  summarise(across(starts_with("month"), ~ sum(.x == 0))) %>%
  pivot_longer(cols = everything(), names_to = "month", values_to = "nobs_missing")

ggplot(obs_missing_month %>%
         mutate(month = factor(month, levels = paste0("month", 1:12))), 
       aes(x = month, y = nobs_missing)) + geom_bar(stat = "identity") +
  ylab("Number of PlotObservationID with missing data") +
  ggtitle("Missing data in S2 time series") +
  theme_minimal()

Add quality flag:

ts_quality_flag <- ts_quality %>%
  rowwise() %>%
  mutate(
    #  If 2 consecutive months of the period March-October are missing
    # quality_flag = 0
    quality_flag = {
      months <- c_across(month3:month10)
      if (any(months[-length(months)] == 0 & months[-1] == 0)) 0 else 1
    }
  ) %>%
  ungroup()
ts_quality_flag %>% count(quality_flag)

Boxplot comparing moments for different indices

GAM_data %>% 
  select(PlotObservationID, index, sos_slope, sos_threshold, pos, eos_slope,
         eos_threshold) %>% distinct() %>%
  pivot_longer(cols = c(sos_slope, sos_threshold, pos, eos_slope, eos_threshold),
               names_to = "moment", values_to = "value") %>%
  ggplot(aes(x = moment, y = value, fill = index)) + geom_boxplot() +
  theme_minimal()

Plot fit and moments for each PlotObservationID

Quality = 1

# Get unique IDs with quality_flag == 1
ids_q1 <- ts_quality_flag %>%
  dplyr::filter(quality_flag == 1) %>%
  mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
  pull(PlotObservationID)
GAM_data_ids_q1 <- GAM_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  # Join to get original values of indices
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
              pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index", 
                           values_to = "value_orig")) %>%
  # Join to get ts_quality data
  left_join(ts_quality_flag %>% select(PlotObservationID, quality_flag)) %>%
  # Keep only those with quality_flag == 1
  dplyr::filter(quality_flag == 1)
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID, DOY, index)`
Joining with `by = join_by(PlotObservationID)`

Save each plot to a file:

Quality = 0

# Get unique IDs with quality_flag == 0
ids_q0 <- ts_quality_flag %>%
  dplyr::filter(quality_flag == 0) %>%
  mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
  pull(PlotObservationID)
GAM_data_ids_q0 <- GAM_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  # Join to get original values of indices
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
              pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index", 
                           values_to = "value_orig")) %>%
  # Join to get ts_quality data
  left_join(ts_quality_flag %>%
              select(PlotObservationID, n_months, quality_flag)) %>%
  # Keep only those with quality_flag == 0
  dplyr::filter(quality_flag == 0)
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID, DOY, index)`
Joining with `by = join_by(PlotObservationID)`

Save each plot to a file:

Smooth the time series of NDMI and NDWI

Using GAM, without replacing values in DOY 1–50 and DOY 315–end with separate base values, later use only unweighted GAM.

compute_unweighted_fit <- function(
    # Data frame df with index values over time (DOY)
    df, 
    # Name of the vegetation indices columns (e.g., "NDVI", "EVI", "SAVI)
    index_cols = c("NDMI", "NDWI")
) {
  # Initialize list to store results
  fits_list <- list()
  
  # Loop over each index column
  for (index_col in index_cols) {
    df_index <- df %>%
      # Remove rows with missing index values and sort data by DOY
      filter(!is.na(.data[[index_col]])) %>% arrange(DOY)
    
    # Extract x (DOY) and y (index) vectors for modelling
    x <- df_index$DOY
    y <- df_index[[index_col]]
    
    # If there are fewer than 11 observations or all values are NA, skip
    if (length(x) < 11 || all(is.na(y))) {
      next
    }
    
    # Fit GAM (unweighted) with a thin plate spline (bs = "tp")
    # to smooth the index curve
    gam_unweighted <- mgcv::bam(y ~ s(x, bs = "tp"))
    pred <- predict(gam_unweighted, newdata = tibble(x = x))
    
    # Create tibble to store original and predicted index values
    fits_df <- tibble(
      PlotObservationID = unique(df$PlotObservationID),
      DOY = x,
      index = index_col,
      value = pred
    )
    
    fits_list[[index_col]] <- fits_df
  }
  
  if (length(fits_list) == 0) {
    return(tibble())
  }
  
  bind_rows(fits_list)
}

Apply the function:

Look:

smoothed_data

Save as an object:

Plot fit and moments for each PlotObservationID

smoothed_data_ids <- smoothed_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  mutate(PlotObservationID = as.character(PlotObservationID))
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID)`

Save each plot to a file:

Get indices data (max. and min.)

Careful! These maximum and minimum values are from the smoothed time series. For NDVI / EVI / SAVI values in DOY 1–50 and DOY 315–end, remember that the GAM smoothing function replaced the original values with the mean base value of observations during each of these respective periods. This was so far not done for NDMI and NDWI.

final_indices_data <- GAM_data %>%
  group_by(PlotObservationID, index) %>%
  summarise(max = max(value), min = min(value)) %>%
  ungroup() %>%
  pivot_wider(names_from = index, values_from = c(max, min),
              names_glue = "{index}_{.value}") %>%
  full_join(
    smoothed_data %>%
      group_by(PlotObservationID, index) %>%
      summarise(max = max(value), min = min(value)) %>%
      ungroup() %>%
      pivot_wider(names_from = index, values_from = c(max, min),
                  names_glue = "{index}_{.value}")
    )
`summarise()` has grouped output by 'PlotObservationID'. You can override using the
`.groups` argument.
`summarise()` has grouped output by 'PlotObservationID'. You can override using the
`.groups` argument.
Joining with `by = join_by(PlotObservationID)`

Get phenology data

Use GAM iter_3 to get dates of the moments, values at those moments and AUC (time-integrated indices) between SOS and EOS:

# Join to get values at SOS, POS, EOS and auc
final_phenology_data <- GAM_data %>%
  mutate(
    stage = case_when(
      DOY == sos_slope ~ "sos_slope",
      DOY == sos_threshold ~ "sos_treshold",
      DOY == pos ~ "pos",
      DOY == eos_slope ~ "eos_slope",
      DOY == eos_threshold ~ "eos_threshold",
      TRUE ~ NA_character_
    )
  ) %>%
  dplyr::filter(!is.na(stage)) %>%
  select(PlotObservationID, index, stage, doy = DOY, value) %>%
  pivot_wider(
    names_from = c(index, stage),
    values_from = c(doy, value),
    names_glue = "{index}_{stage}_{.value}"
  ) %>%
  # Convert list cols to regular numeric cols
  mutate(
    NDVI_sos_slope_value = map_dbl(NDVI_sos_slope_value, 1),
    NDVI_sos_treshold_value = map_dbl(NDVI_sos_treshold_value, 1),
    NDVI_pos_value = map_dbl(NDVI_pos_value, 1),
    NDVI_eos_slope_value = map_dbl(NDVI_eos_slope_value, 1),
    NDVI_eos_threshold_value = map_dbl(NDVI_eos_threshold_value, 1),
    EVI_sos_slope_value = map_dbl(EVI_sos_slope_value, 1),
    EVI_sos_threshold_value = map_dbl(EVI_sos_treshold_value, 1),
    EVI_pos_value = map_dbl(EVI_pos_value, 1),
    EVI_eos_slope_value = map_dbl(EVI_eos_slope_value, 1),
    EVI_eos_threshold_value = map_dbl(EVI_eos_threshold_value, 1),
    SAVI_sos_slope_value = map_dbl(SAVI_sos_slope_value, 1),
    SAVI_sos_threshold_value = map_dbl(SAVI_sos_treshold_value, 1),
    SAVI_pos_value = map_dbl(SAVI_pos_value, 1),
    SAVI_eos_slope_value = map_dbl(SAVI_eos_slope_value, 1),
    SAVI_eos_threshold_value = map_dbl(SAVI_eos_threshold_value, 1)
  ) %>%
  full_join(GAM_data %>%
              distinct(PlotObservationID, index, auc_slope, auc_threshold) %>%
              pivot_wider(names_from = index, values_from = c(auc_slope, auc_threshold),
                          names_glue = "{index}_{.value}"))
Joining with `by = join_by(PlotObservationID)`

Join indices and phenology data

final_RS_data <- full_join(
  # Indices data (max and min)
  final_indices_data,
  # Average values of indices per month
  monthly_avg_indices %>%
    pivot_wider(names_from = index, values_from = c(avg_value_01:avg_value_12),
                names_glue = "{index}_{.value}")
  ) %>%
  full_join(
    # Phenology data
    final_phenology_data 
    ) %>%
  # Sort cols in alphabetical order
  select(PlotObservationID, sort(names(.)[names(.) != "PlotObservationID"]))
Joining with `by = join_by(PlotObservationID)`
Joining with `by = join_by(PlotObservationID)`

Add EUNIS codes

final_RS_data <- final_RS_data %>% left_join(db_Europa_allobs)
Joining with `by = join_by(PlotObservationID)`
data_RS_S2_bands_indices <- data_RS_S2_bands_indices %>%
  left_join(db_Europa_allobs)
Joining with `by = join_by(PlotObservationID, EUNISa_1, EUNISa_1_descr, EUNISa_2,
EUNISa_2_descr)`

HERE: REVISE: Monthly spectrophenology per habitat type

# Prepare the data
data_monthly_EUNISa_1 <- data_RS_S2_bands_indices %>%
  mutate(month = month(date, label = TRUE, abbr = TRUE)) %>%
  group_by(month, EUNISa_1, EUNISa_1_descr) %>%
  summarise(
    mean_NDVI = mean(NDVI, na.rm = TRUE),
    sd_NDVI = sd(NDVI, na.rm = TRUE),
    n_NDVI = sum(!is.na(NDVI)),
    mean_EVI = mean(EVI, na.rm = TRUE),
    sd_EVI = sd(EVI, na.rm = TRUE),
    n_EVI = sum(!is.na(EVI)),
    mean_SAVI = mean(SAVI, na.rm = TRUE),
    sd_SAVI = sd(SAVI, na.rm = TRUE),
    n_SAVI = sum(!is.na(SAVI)),
    .groups = "drop"
  )
data_monthly_EUNISa_2 <- data_RS_S2_bands_indices %>%
  mutate(month = month(date, label = TRUE, abbr = TRUE)) %>%
  group_by(month, EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr) %>%
  summarise(
    mean_NDVI = mean(NDVI, na.rm = TRUE),
    sd_NDVI = sd(NDVI, na.rm = TRUE),
    n_NDVI = sum(!is.na(NDVI)),
    mean_EVI = mean(EVI, na.rm = TRUE),
    sd_EVI = sd(EVI, na.rm = TRUE),
    n_EVI = sum(!is.na(EVI)),
    mean_SAVI = mean(SAVI, na.rm = TRUE),
    sd_SAVI = sd(SAVI, na.rm = TRUE),
    n_SAVI = sum(!is.na(SAVI)),
    .groups = "drop"
  )
# Plots

# EUNISa_1
ggplot(data_monthly_EUNISa_1, 
       aes(x = month, y = mean_NDVI, color = EUNISa_1, group = EUNISa_1)) +
  geom_point(aes(size = n_NDVI)) +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNISa_1)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_1, 
       aes(x = month, y = mean_EVI, color = EUNISa_1, group = EUNISa_1)) +
  geom_point(aes(size = n_EVI)) +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNISa_1)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_1, 
       aes(x = month, y = mean_SAVI, color = EUNISa_1, group = EUNISa_1)) +
  geom_point(aes(size = n_SAVI)) +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNISa_1)"
  ) +
  theme_minimal()

# EUNISa_2

# NDVI
ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_NDVI)) +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_NDVI)) +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_NDVI)) +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_NDVI)) +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

# EVI
ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_EVI)) +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_EVI)) +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_EVI)) +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_EVI)) +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

# SAVI
ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_SAVI)) +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_SAVI)) +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_SAVI)) +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

ggplot(data_monthly_EUNISa_2 %>% filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point(aes(size = n_SAVI)) +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNISa_2)"
  ) +
  theme_minimal()

MODIFY: Calculate other phenological metrics

final_RS_data <- final_RS_data %>%
  mutate(
    # Growing season duration
    NDVI_gsd = NDVI_eos_doy - NDVI_sos_doy,
    EVI_gsd = NDVI_eos_doy - NDVI_sos_doy,
    SAVI_gsd = SAVI_eos_doy - SAVI_sos_doy,
    # Difference in value between pos and sos
    NDVI_diff_pos_sos_value = NDVI_pos_value - NDVI_sos_value,
    EVI_diff_pos_sos_value = EVI_pos_value - EVI_sos_value,
    SAVI_diff_pos_sos_value = SAVI_pos_value - SAVI_sos_value,
    # Difference in value between pos and eos
    NDVI_diff_pos_eos_value = NDVI_pos_value - NDVI_eos_value,
    EVI_diff_pos_eos_value = EVI_pos_value - EVI_eos_value,
    SAVI_diff_pos_eos_value = SAVI_pos_value - SAVI_eos_value,
    # Difference in doy between pos and sos
    NDVI_diff_pos_sos_doy = NDVI_pos_doy - NDVI_sos_doy,
    EVI_diff_pos_sos_doy = EVI_pos_doy - EVI_sos_doy,
    SAVI_diff_pos_sos_doy = SAVI_pos_doy - SAVI_sos_doy,
    # Difference in doy between eos and pos
    NDVI_diff_eos_pos_doy = NDVI_eos_doy - NDVI_pos_doy,
    EVI_diff_eos_pos_doy = EVI_eos_doy - EVI_pos_doy,
    SAVI_diff_eos_pos_doy = SAVI_eos_doy - SAVI_pos_doy
  )

Checks

# Growing season duration should be positive
nrow(final_RS_data %>% 
       dplyr::filter(NDVI_gsd <= 0 | EVI_gsd <= 0 | SAVI_gsd <= 0))
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_value <= 0))
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_sos_value <= 0))
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_sos_value <= 0))
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_eos_value <= 0))
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_eos_value <= 0))
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_eos_value <= 0))
# Difference in doy between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_doy <= 0 | EVI_diff_pos_sos_doy <= 0 |
                       SAVI_diff_pos_sos_doy <= 0))
# Difference in doy between eos and pos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_eos_pos_doy <= 0 | EVI_diff_eos_pos_doy <= 0 |
                       SAVI_diff_eos_pos_doy <= 0))

HERE: Run. Detect number of peaks in smoothed curves

# Set up parallel plan
plan(multisession, workers = min(parallel::detectCores() - 1))

# Define peak-counting function
count_peaks <- function(df) {
  # Convert 1D array column to numeric vector
  y <- as.numeric(df$value)
  
  peaks <- findpeaks(y, minpeakdistance = 30, threshold = 0.02)
  
  tibble(
    PlotObservationID = unique(df$PlotObservationID),
    index = unique(df$index),
    num_peaks = if (!is.null(peaks)) nrow(peaks) else 0
  )
}

# Apply peak counting in parallel
peak_counts <- GAM_data %>%
  filter(fit_type == "iter_3") %>%
  arrange(DOY) %>%
  group_by(PlotObservationID, index) %>%
  nest() %>%
  mutate(result = future_map(data, count_peaks, .progress = TRUE,
                             .options = furrr_options(scheduling = Inf))) %>%
  select(-data) %>%
  unnest(result)

# Summarize result
peak_counts_summary <- peak_counts %>% count(index, num_peaks)

HERE: save

# # Function to count peaks for each PlotObservationID
# count_peaks <- function(df) {
#   y <- df$value
#   peaks <- findpeaks(y, 
#                      # Minimum number of indices (e.g., DOY steps)
#                      # between two peaks
#                      minpeakdistance = 30, 
#                      # Minimum vertical difference between a peak
#                      # and its surrounding value
#                      threshold = 0.02)
#   num_peaks <- if (!is.null(peaks)) nrow(peaks) else 0
#   return(tibble(PlotObservationID = unique(df$PlotObservationID),
#                 num_peaks = num_peaks))
# }
# 
# # Apply to each group
# peak_counts <- GAM_data %>%
#   mutate(value = map_dbl(value, 1)) %>%
#   dplyr::filter(fit_type == "iter_3") %>%
#   arrange(DOY) %>%
#   group_by(PlotObservationID, index) %>%
#   group_modify(~ count_peaks(.x)) %>%
#   ungroup()
# 
# # View result
# peak_counts %>% count(index, num_peaks)

Plot number of peaks

peak_counts %>% count(index, num_peaks) %>%
  ggplot(aes(x = index, y = n, fill = factor(num_peaks))) +
  geom_bar(stat = "identity", position = position_dodge())

EVI gives less problems, maybe use only this one?

Add number of peaks to data

final_RS_data <- final_RS_data %>%
  left_join(peak_counts %>%
              pivot_wider(names_from = index, values_from = num_peaks,
                          names_glue = "{index}_{.value}"))

Plot fit and moments for PlotObservationIDs with zero peaks

Further checks (EVI)

# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_sos_value <= 0))
final_RS_data %>%
  dplyr::filter(EVI_diff_pos_sos_value <= 0) %>%
  count(EVI_num_peaks)
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_eos_value <= 0))
final_RS_data %>%
  dplyr::filter(EVI_diff_pos_eos_value <= 0) %>%
  count(EVI_num_peaks)

Add some columns needed

final_RS_data <- final_RS_data %>%
  left_join(
    data_RS_S2_bands_indices %>%
      distinct(PlotObservationID, year, biogeo, unit, Lctnmth)
    )

Add canopy height data

Read the data:

data_RS_CH <- read_csv(
  "C:/Data/MOTIVATE/MOTIVATE_RS_data/Canopy_Height_1m/Europe_points_CanopyHeight_1m.csv")
db_Europa <- read_csv(
  here("..", "DB_first_check", "data", "clean","db_Europa_20250107.csv")
  )
data_RS_CH_ID <- db_Europa %>%
  select(PlotObservationID, obs_unique_id) %>%
  right_join(data_RS_CH %>%
              # Rename to be able to join on this column
              rename(obs_unique_id = obs_unique)) %>%
  select(PlotObservationID, canopy_height)

Join:

final_RS_data <- final_RS_data %>%
  left_join(data_RS_CH_ID %>%
              mutate(PlotObservationID = factor(PlotObservationID)))

Save to clean data

write_tsv(final_RS_data,
          here("data", "clean","final_RS_data_bands_S2_all.csv"))

Session info

sessionInfo()
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB3b3JrIHdpdGggUzIgYmFuZHMgZGVyaXZlZCBmcm9tIEdFRSINCnN1YnRpdGxlOiAiUmVhZCBhbmQgbWFuaXB1bGF0aW9uIGRhdGEsIGNhbGN1bGF0ZSBpbmRpY2VzIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkoc2lnbmFsKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZHRwbHlyKQ0KbGlicmFyeShzZikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KG1nY3YpDQpsaWJyYXJ5KGZ1dHVyZSkNCmxpYnJhcnkoZnVycnIpDQpsaWJyYXJ5KHByb2dyZXNzcikNCmxpYnJhcnkocHJhY21hKQ0KYGBgDQoNCiMgU2V0IGEgc2ltcGxlIGNvbnNvbGUgcHJvZ3Jlc3MgYmFyDQoNCmBgYHtyfQ0KaGFuZGxlcnMoInR4dHByb2dyZXNzYmFyIikgIyBTaW1wbGUgY29uc29sZSBwcm9ncmVzcyBiYXINCmBgYA0KDQojIFN1cHJlc3MgcGFja2FnZSBtZXNzYWdlcw0KDQpgYGB7cn0NCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQogIGxpYnJhcnkobWdjdikNCiAgbGlicmFyeShubG1lKQ0KfSkNCmBgYA0KDQojIExvYWQgcHJldmlvdXNseSBjcmVhdGVkIG9iamVjdHMNCg0KYGBge3J9DQpsb2FkKGZpbGUgPSAib2JqZWN0cy9kYXRhX1JTX1MyX2JhbmRzX2luZGljZXMuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvR0FNX2RhdGFfUzIuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvdHNfcGxvdHNfcTFfUzIuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvdHNfcGxvdHNfcTBfUzIuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpgYGANCg0KIyBMb2FkIFJlc3VydmV5IGRiDQoNCmBgYHtyfQ0KZGJfRXVyb3BhX2FsbG9icyA8LSByZWFkX2NzdigNCiAgaGVyZSgiZGF0YSIsICJjbGVhbiIsICJkYl9FdXJvcGFfYWxsb2JzLmNzdiIpKSAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IpICU+JQ0KICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBmYWN0b3IoUGxvdE9ic2VydmF0aW9uSUQpLA0KICAgICAgICAgRVVOSVNhXzEgPSBmYWN0b3IoRVVOSVNhXzEpLCBFVU5JU2FfMiA9IGZhY3RvcihFVU5JU2FfMikpDQpgYGANCg0KIyBEZWZpbmUgcHJpbnRhbGwgZnVuY3Rpb24NCg0KYGBge3J9DQpwcmludGFsbCA8LSBmdW5jdGlvbih0aWJibGUpIHsNCiAgcHJpbnQodGliYmxlLCB3aWR0aCA9IEluZikNCiAgfQ0KYGBgDQoNCiMgUmVhZCBmaWxlcyB3aXRoIGJhbmQgZGF0YQ0KDQpJIGdvdCB0aGVzZSBmaWxlcyB1c2luZyB0aGUgR0VFIGNvZGUgcHJlcGFyZWQgYnkgQmVhLg0KDQpUaGVzZSBmaWxlcyBjb250YWluIGFsbCBvYnNlcnZhdGlvbnMgaW4gdGhlIFJlU3VydmV5IGRhdGFiYXNlIGZyb20gMjAxNiBvbndhcmQuIEluIG9yZGVyIHRvIGF2b2lkIGNvbXB1dGF0aW9uIHByb2JsZW1zIGluIEdFRSwgYmlvZ2VvZ3JhcGhpY2FsIHVuaXRzIHRoYXQgY29udGFpbiBtb3JlIHRoYW4gNDUwMCBwb2ludHMgaGF2ZSBiZWVuIHN1YmRpdmlkZWQgaW4gQXJjR0lTLg0KDQpgYGB7cn0NCiMgU2V0IHRoZSBmb2xkZXIgcGF0aA0KZm9sZGVyX3BhdGggPC0gIkM6L0RhdGEvTU9USVZBVEUvTU9USVZBVEVfUlNfZGF0YS9TMi9CYW5kcy9hbGwiDQoNCiMgTGlzdCBDU1YgZmlsZXMNCmNzdl9maWxlcyA8LSBsaXN0LmZpbGVzKGZvbGRlcl9wYXRoLCBmdWxsLm5hbWVzID0gVFJVRSwgcmVjdXJzaXZlID0gVFJVRSkNCg0KIyBGdW5jdGlvbiB0byBleHRyYWN0IGJpb2dlbyBhbmQgdW5pdCBmcm9tIHRoZSBmaWxlbmFtZQ0KZXh0cmFjdF9pbmZvIDwtIGZ1bmN0aW9uKGZpbGVuYW1lKSB7DQogIGZpcnN0X3dvcmQgPC0gc3Ryc3BsaXQoZmlsZW5hbWUsICJfIilbWzFdXVsxXQ0KICBiaW9nZW8gPC0gc3RyX2V4dHJhY3QoZmlyc3Rfd29yZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICJeKEFMUHxBTkF8QVJDfEFUTHxCTEFDS1NFQXxCT1J8Q09OfE1BQ0FST05FU0lBfE1FRHxQQU5PTklBfFNURVBQSUMpIikNCiAgdW5pdCA8LSBzdHJfcmVtb3ZlKGZpcnN0X3dvcmQsIGJpb2dlbykNCiAgaWYgKGlzLm5hKHVuaXQpIHx8IHVuaXQgPT0gIiIpIHVuaXQgPC0gTkFfY2hhcmFjdGVyXw0KICBsaXN0KGJpb2dlbyA9IGJpb2dlbywgdW5pdCA9IHVuaXQpDQogIH0NCg0KDQojIERlZmluZSBjb2x1bW4gdHlwZXM6IGZvcmNlIFJTcnZ5cGwgdG8gY2hhcmFjdGVyLCBvdGhlcnMgYXV0by1kZXRlY3RlZA0KY3VzdG9tX2NvbF90eXBlcyA8LSBjb2xzKA0KICBSU3J2eXBsID0gY29sX2NoYXJhY3RlcigpLA0KICBSU3J2eXN0ID0gY29sX2NoYXJhY3RlcigpLA0KICBkZWZhdWx0ID0gY29sX2d1ZXNzKCkNCikNCg0KIyBSZWFkIGFuZCBwcm9jZXNzIGVhY2ggZmlsZQ0KZGF0YV9saXN0IDwtIGxhcHBseShjc3ZfZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHsNCiAgaW5mbyA8LSBleHRyYWN0X2luZm8oYmFzZW5hbWUoZmlsZSkpICMgVXNlIG9ubHkgdGhlIGZpbGVuYW1lDQogIA0KICAjIFJlYWQgdGhlIGZpbGUNCiAgZGYgPC0gcmVhZF9jc3YoZmlsZSwgY29sX3R5cGVzID0gY3VzdG9tX2NvbF90eXBlcykgJT4lDQogICAgIyBSZW1vdmUgY29sdW1ucyB0aGF0IGdpdmUgY29sdW1uIHR5cGUgcHJvYmxlbXMgd2hlbiBjb21iaW5pbmcgZGF0YQ0KICAgIHNlbGVjdCgtc3RhcnRzX3dpdGgoIkVVTklTIiksIC1zdGFydHNfd2l0aCgiUmVTdXJ2ZXkiKSkgJT4lDQogICAgbXV0YXRlKGJpb2dlbyA9IGluZm8kYmlvZ2VvLCB1bml0ID0gaW5mbyR1bml0KQ0KICANCiAgcmV0dXJuKGRmKQ0KICB9KQ0KDQojIENvbWJpbmUgYWxsIGRhdGENCmRhdGFfUlNfUzJfYmFuZHMgPC0gYmluZF9yb3dzKGRhdGFfbGlzdCkgJT4lDQogIHJlbmFtZShQbG90T2JzZXJ2YXRpb25JRCA9IFBsdE9iSUQpDQoNCiMgVmlldyB0aGUgcmVzdWx0aW5nIHRpYmJsZQ0KcHJpbnQoZGF0YV9SU19TMl9iYW5kcykNCg0KIyBDb3VudHMgcGVyIGJpb2dlbyBhbmQgdW5pdA0KcHJpbnQoZGF0YV9SU19TMl9iYW5kcyAlPiUgY291bnQoYmlvZ2VvLCB1bml0KSwgbiA9IDEwMCkNCmBgYA0KDQojIFNvbWUgY2hlY2tzDQoNCkNoZWNrIHRoYXQgdGhlIHllYXIgaW4gdGhlIGRhdGUgb2YgdGhlIGltYWdlcyBpcyBub3QgZGlmZmVyZW50IHRvIHRoZSBzYW1wbGluZyB5ZWFyOg0KDQpgYGB7cn0NCmRhdGFfUlNfUzJfYmFuZHMgJT4lIGRwbHlyOjpmaWx0ZXIoeWVhciAhPSB5ZWFyKGRhdGUpKQ0KYGBgDQoNCkNoZWNrIGhvdyBtYW55IGRpZmZlcmVudCBpbWFnZXMgYXJlIGZvciBlYWNoIG9ic2VydmF0aW9uLCBkYXRlIGFuZCB0aW1lOg0KDQpgYGB7cn0NCmRhdGFfUlNfUzJfYmFuZHMgJT4lIGdyb3VwX2J5KFBsb3RPYnNlcnZhdGlvbklELCBkYXRlLCB0aW1lX3V0YykgJT4lDQogIHN1bW1hcmlzZShuX2ltYWdlcyA9IG5fZGlzdGluY3QoaW1hZ2VfaWQpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgY291bnQobl9pbWFnZXMpDQpgYGANCg0KIyBBdmVyYWdlIHRoZSBiYW5kcw0KDQpXaGVuIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgaW1hZ2UgZm9yIGVhY2ggcG9pbnQgYW5kIGRheSwgYXZlcmFnZSB0aGUgdmFsdWVzIG9mIHRoZSBiYW5kczoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgU3VtbWFyaXplIHRoZSBiYW5kIHZhbHVlcyBjb25kaXRpb25hbGx5DQpiYW5kX3N1bW1hcnkgPC0gZGF0YV9SU19TMl9iYW5kcyAlPiUNCiAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGRhdGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbl9pbWFnZXMgPSBuX2Rpc3RpbmN0KGltYWdlX2lkKSwNCiAgICBCMTEgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEIxMSwgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEIxMSksDQogICAgQjIgID0gaWYgKG5faW1hZ2VzID4gMSkgbWVhbihCMiwgIG5hLnJtID0gVFJVRSkgZWxzZSBmaXJzdChCMiksDQogICAgQjMgID0gaWYgKG5faW1hZ2VzID4gMSkgbWVhbihCMywgIG5hLnJtID0gVFJVRSkgZWxzZSBmaXJzdChCMyksDQogICAgQjQgID0gaWYgKG5faW1hZ2VzID4gMSkgbWVhbihCNCwgIG5hLnJtID0gVFJVRSkgZWxzZSBmaXJzdChCNCksDQogICAgQjggID0gaWYgKG5faW1hZ2VzID4gMSkgbWVhbihCOCwgIG5hLnJtID0gVFJVRSkgZWxzZSBmaXJzdChCOCksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApIA0KDQojIENhbGN1bGF0ZSBob3cgbWFueSBkaWZmZXJlbnQgZGF5cyBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRA0Kbl9kYXlzIDwtIGJhbmRfc3VtbWFyeSAlPiUNCiAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICBzdW1tYXJpc2Uobl9kYXlzID0gbl9kaXN0aW5jdChkYXRlKSkNCg0KIyBKb2luIGJhY2sgdG8gb3JpZ2luYWwgZGF0YQ0KZGF0YV9SU19TMl9iYW5kc191cGRhdGVkIDwtIGRhdGFfUlNfUzJfYmFuZHMgJT4lDQogICMgUmVtb3ZlIG9sZCBiYW5kIHZhbHVlcw0KICBzZWxlY3QoLUIxMSwgLUIyLCAtQjMsIC1CNCwgLUI4KSAlPiUNCiAgIyBKb2luIGJhbmRfc3VtbWFyeQ0KICBsZWZ0X2pvaW4oYmFuZF9zdW1tYXJ5LCBieSA9IGMoIlBsb3RPYnNlcnZhdGlvbklEIiwgImRhdGUiKSkgJT4lDQogICMgS2VlcCBvbmUgcm93IHBlciBncm91cA0KICBkaXN0aW5jdChQbG90T2JzZXJ2YXRpb25JRCwgZGF0ZSwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogICMgUmVtb3ZlIHVud2FudGVkIGNvbHVtbnMNCiAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLWltYWdlX2lkLCAtLmdlbywgLXRpbWVfdXRjLCAtdGltZXN0YW1wKSAlPiUNCiAgIyBKb2luDQogIGxlZnRfam9pbihuX2RheXMpDQpgYGANCg0KIyBDYWxjdWxhdGUgaW5kaWNlcw0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBDYWxjdWxhdGUgaW5kaWNlcw0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzIDwtIGRhdGFfUlNfUzJfYmFuZHNfdXBkYXRlZCAlPiUNCiAgIyBTZXQgUGxvdE9ic2VydmF0aW9uSUQgYXMgZmFjdG9yDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGZhY3RvcihQbG90T2JzZXJ2YXRpb25JRCkpICU+JQ0KICAjIFJlbmFtZSB0aGUgYmFuZHMNCiAgcmVuYW1lKGJsdWUgPSBCMiwgZ3JlZW4gPSBCMywgcmVkID0gQjQsIE5JUiA9IEI4LCBTV0lSID0gQjExKSAlPiUNCiAgIyBTY2FsZSB0aGUgYmFuZHMNCiAgbXV0YXRlKGJsdWUgPSBibHVlIC8gMTAwMDAsIGdyZWVuID0gZ3JlZW4gLyAxMDAwMCwgcmVkID0gcmVkIC8gMTAwMDAsDQogICAgICAgICBOSVIgPSBOSVIgLyAxMDAwMCwgU1dJUiA9IFNXSVIgLyAxMDAwMCkgJT4lDQogICMgQ3JlYXRlIGNvbHVtbiB0aGF0IGNvbWJpbmVzIHRoZSBkYXkgb2YgdGhlIG1vbnRoIGFuZCB0aGUgdGltZQ0KICBtdXRhdGUoDQogICAgZGF0ZSA9IGFzLlBPU0lYY3QoZGF0ZSksDQogICAgIyBOb3JtYWxpemUgdGhlIGRhdGVzIHRvIGEgZml4ZWQgeWVhciAoMjAwMCkNCiAgICAjIHNvIHRoYXQgc2Vhc29uYWwgcGF0dGVybnMgYWNyb3NzIGRpZmZlcmVudCB5ZWFycyBjYW4gYmUgY29tcGFyZWQgdmlzdWFsbHkNCiAgICBkYXlfbW9udGggPSBhcy5QT1NJWGN0KGZvcm1hdChkYXRlLCAiMjAwMC0lbS0lZCIpKSkgJT4lDQogICMgQ3JlYXRlIGNvbHVtbiB3aXRoIERPWQ0KICBtdXRhdGUoRE9ZID0geWRheShkYXRlKSkgJT4lDQogICMgQ2FsY3VsYXRlIE5EVkkNCiAgbXV0YXRlKE5EVkkgPSAoTklSIC0gcmVkKSAvIChOSVIgKyByZWQpLA0KICAgICAgICAgRVZJID0gKE5JUiAtIHJlZCkgKiAyLjUgLyAoTklSICsgNiAqIHJlZCAtIDcuNSAqIGJsdWUgKyAxKSwNCiAgICAgICAgIFNBVkkgPSAoTklSIC0gcmVkKSAqIDEuNSAvIChOSVIgKyByZWQgKyAwLjUpLA0KICAgICAgICAgTkRNSSA9IChOSVIgLSBTV0lSKSAvIChOSVIgKyBTV0lSKSwNCiAgICAgICAgIE5EV0kgPSAoZ3JlZW4gLSBOSVIpIC8gKGdyZWVuICsgTklSKSkgJT4lDQogICMgU2V0dGluZyB2YWx1ZXMgb2YgaW5kaWNlcyBvdXRzaWRlIGV4cGVjdGVkIHJhbmdlcyAoZXJyb3JzKSB0byBOQQ0KICBtdXRhdGUoRVZJID0gaWZfZWxzZShFVkkgPiAxIHwgRVZJIDwgLTEsIE5BLCBFVkkpKSAjIDEyMTY2IHZhbHVlcyBvZiBFVkkgYXMgTkENCmBgYA0KDQpTYXZlOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMsIGZpbGUgPSAib2JqZWN0cy9kYXRhX1JTX1MyX2JhbmRzX2luZGljZXMuUmRhdGEiKQ0KYGBgDQoNClBsb3Qgbl9kYXl0aW1lOg0KDQpgYGB7cn0NCmRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICBzdW1tYXJpc2Uobl9kYXlzID0gZmlyc3Qobl9kYXlzKSkgJT4lIHVuZ3JvdXAoKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbl9kYXlzKSkgKyBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMgQ29tcHV0ZSBwaGVub2xvZ2ljYWwgbWV0cmljcyBmcm9tIG1vZGVscyBmaXR0ZWQgdG8gdGltZSBzZXJpZXMgZGF0YQ0KDQojIyBGdW5jdGlvbg0KDQpVc2luZyBHQU1zLCByZXdlaWdodGluZyBhbmQgMyBpdGVyYXRpb25zLg0KDQpVc2luZyBib3RoIGEgY2hhbmdlIGRldGVjdGlvbiBtZXRob2QgKG1heGltdW0gc2xvcGUpIGFuZCBhIHRocmVzaG9sZCBtZXRob2QgKDUwJSBhbXBsaXR1ZGUpIHRvIGNhbGN1bGF0ZSBzb3MgYW5kIGVvcy4NCg0KQXBwcm9hY2ggc2ltaWxhciB0byBodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmphZy4yMDIwLjEwMjE3MiBmb3IgR0FNIGZpdHRpbmcgYW5kIGNoYW5nZSBkZXRlY3Rpb24gbWV0aG9kLCBhbmQgdG8gaHR0cHM6Ly93d3cubWRwaS5jb20vMjA3Mi00MjkyLzEyLzIyLzM3MzggZm90IHRocmVzaG9sZCBtZXRob2QuDQoNCkRlZmluZSBmdW5jdGlvbiB0byBjb21wdXRlIHBoZW5vbG9neSBtZXRyaWNzIHVzaW5nIEdBTSBmaXQgYW5kIE5EVkkgLyBFVkkgLyBTQVZJOg0KDQpgYGB7cn0NCmNvbXB1dGVfbWV0cmljc19tb2RlbHMgPC0gZnVuY3Rpb24oZGYsIGluZGV4X2NvbHMgPSBjKCJORFZJIiwgIkVWSSIsICJTQVZJIikpIHsNCiAgc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsNCiAgICBsaWJyYXJ5KG1nY3YpDQogICAgbGlicmFyeShubG1lKQ0KICAgIH0pDQogIA0KICBwbGFuKG11bHRpc2Vzc2lvbikgICMgU2V0IHVwIHBhcmFsbGVsIHByb2Nlc3NpbmcNCiAgDQogICMgQ3JlYXRlIGEgbGlzdCBvZiBpbmRleC1zcGVjaWZpYyBkYXRhIGZyYW1lcw0KICBpbmRleF9kZnMgPC0gbGFwcGx5KGluZGV4X2NvbHMsIGZ1bmN0aW9uKGluZGV4X2NvbCkgew0KICAgIGxpc3QoaW5kZXhfY29sID0gaW5kZXhfY29sLCBkZiA9IGRmICU+JQ0KICAgICAgICAgICBzZWxlY3QoRE9ZLCBQbG90T2JzZXJ2YXRpb25JRCwgYWxsX29mKGluZGV4X2NvbCkpKQ0KICAgIH0pDQogIA0KICAjIERlZmluZSB0aGUgcHJvY2Vzc2luZyBmdW5jdGlvbiBmb3IgZWFjaCBpbmRleA0KICBwcm9jZXNzX2luZGV4IDwtIGZ1bmN0aW9uKGluZGV4X2RhdGEpIHsNCiAgICBpbmRleF9jb2wgPC0gaW5kZXhfZGF0YSRpbmRleF9jb2wNCiAgICBkZl9pbmRleCA8LSBpbmRleF9kYXRhJGRmICU+JQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUNCiAgICAgIGFycmFuZ2UoRE9ZKQ0KICAgIA0KICAgIHBsb3RfaWQgPC0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKQ0KDQogICAgaWYgKG5yb3coZGZfaW5kZXgpIDwgMTApIHsNCiAgICAgIG1lc3NhZ2UoIiAgU2tpcHBlZDogaW5zdWZmaWNpZW50IGRhdGEgKDwgMTAgcm93cykiKQ0KICAgICAgcmV0dXJuKHRpYmJsZShQbG90T2JzZXJ2YXRpb25JRCA9IHBsb3RfaWQsIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgICAgICAgICAgICBzb3Nfc2xvcGUgPSBOQV9yZWFsXywgc29zX3RocmVzaG9sZCA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBwb3MgPSBOQV9yZWFsXywgZW9zX3Nsb3BlID0gTkFfcmVhbF8sIA0KICAgICAgICAgICAgICAgICAgICBlb3NfdGhyZXNob2xkID0gTkFfcmVhbF8sIGF1Y19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIFZtYXggPSBOQV9yZWFsXywNCiAgICAgICAgICAgICAgICAgICAgRE9ZID0gZGZfaW5kZXgkRE9ZLCB2YWx1ZSA9IE5BX3JlYWxfKSkNCiAgICB9DQogICAgDQogICAgIyBSZXBsYWNlIGVhcmx5L2xhdGUgRE9ZIHZhbHVlcw0KICAgIGJhc2VfdmFsdWVfZWFybHkgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA8PSA1MCkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoaW5kZXhfY29sKSwgbmEucm0gPSBUUlVFKQ0KICAgIGJhc2VfdmFsdWVfbGF0ZSAgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA+PSAzMTUpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKGluZGV4X2NvbCksIG5hLnJtID0gVFJVRSkNCg0KICAgIGRmX2luZGV4IDwtIGRmX2luZGV4ICU+JQ0KICAgICAgbXV0YXRlKCEhaW5kZXhfY29sIDo9IGNhc2Vfd2hlbigNCiAgICAgICAgRE9ZIDw9IDUwIH4gYmFzZV92YWx1ZV9lYXJseSwNCiAgICAgICAgRE9ZID49IDMxNSB+IGJhc2VfdmFsdWVfbGF0ZSwNCiAgICAgICAgVFJVRSB+IC5kYXRhW1tpbmRleF9jb2xdXQ0KICAgICAgKSkNCg0KICAgIHggPC0gZGZfaW5kZXgkRE9ZDQogICAgeSA8LSBkZl9pbmRleFtbaW5kZXhfY29sXV0NCiAgICB3ZWlnaHRzIDwtIHJlcCgxLCBsZW5ndGgoeSkpDQogICAgDQogICAgIyBHQU0gZml0DQogICAgcHJlZCA8LSBOVUxMDQogICAgZm9yIChpIGluIDE6Mykgew0KICAgICAgZ2FtX2ZpdCA8LSB0cnlDYXRjaCh7DQogICAgICAgIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpLHdlaWdodHMgPSB3ZWlnaHRzKQ0KICAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICAgICAgICBtZXNzYWdlKCIgIEdBTSBmaXR0aW5nIGZhaWxlZCBmb3IgIiwgcGxvdF9pZCwgIiAtICIsIGluZGV4X2NvbCwgIjogIiwgDQogICAgICAgICAgICAgICAgICBlJG1lc3NhZ2UpDQogICAgICAgICAgcmV0dXJuKE5VTEwpDQogICAgICAgICAgfSkNCiAgICAgIGlmIChpcy5udWxsKGdhbV9maXQpKSB7DQogICAgICAgIHJldHVybih0aWJibGUoDQogICAgICAgICAgUGxvdE9ic2VydmF0aW9uSUQgPSBwbG90X2lkLA0KICAgICAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgIHNvc19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgIHNvc190aHJlc2hvbGQgPSBOQV9yZWFsXywNCiAgICAgICAgICBwb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBlb3Nfc2xvcGUgPSBOQV9yZWFsXywgDQogICAgICAgICAgZW9zX3RocmVzaG9sZCA9IE5BX3JlYWxfLCANCiAgICAgICAgICBhdWNfc2xvcGUgPSBOQV9yZWFsXywNCiAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcHJlID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcG9zdCA9IE5BX3JlYWxfLA0KICAgICAgICAgIFZtYXggPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9zb3MgPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9lb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBET1kgPSBkZl9pbmRleCRET1ksDQogICAgICAgICAgdmFsdWUgPSBOQV9yZWFsXykpDQogICAgICAgIH0NCiAgICAgIA0KICAgICAgcHJlZCA8LSB0cnlDYXRjaCh7DQogICAgICAgIHByZWRpY3QoZ2FtX2ZpdCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgICAgIG1lc3NhZ2UoIlByZWRpY3Rpb24gZmFpbGVkIGZvciAiLCBwbG90X2lkLCAiIC0gIiwgaW5kZXhfY29sLCAiOiAiLA0KICAgICAgICAgICAgICAgICAgZSRtZXNzYWdlKQ0KICAgICAgICAgIHJldHVybihyZXAoTkFfcmVhbF8sIGxlbmd0aCh4KSkpDQogICAgICAgICAgfSkNCiAgICAgIA0KICAgICAgaWR4X2JldHdlZW4gPC0gd2hpY2goeCA+IDUwICYgeCA8IDMxNSAmICFpcy5uYShwcmVkKSAmIHByZWQgIT0gMCkNCiAgICAgIHdlaWdodHMgPC0gcmVwKDEsIGxlbmd0aCh5KSkNCiAgICAgIHdlaWdodHNbaWR4X2JldHdlZW5dIDwtICh5W2lkeF9iZXR3ZWVuXSAvIChwcmVkW2lkeF9iZXR3ZWVuXSArIDFlLTYpKV40DQogICAgICB3ZWlnaHRzW3dlaWdodHMgPiAxIHwgaXMubmEod2VpZ2h0cyldIDwtIDENCiAgICAgIH0NCiAgICANCiAgICAjIENvbXB1dGUgbWV0cmljcw0KICAgIHNsb3BlIDwtIGMoTkEsIGRpZmYocHJlZCkpDQogICAgaWR4IDwtIHdoaWNoKHggPj0gNTAgJiB4IDw9IDMxNSkNCiAgICBwb3MgPC0gaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1heChwcmVkW2lkeF0pXSBlbHNlIE5BX3JlYWxfDQoNCiAgICBzb3Nfc2xvcGUgPC0gaWYgKCFpcy5uYShwb3MpKSB7DQogICAgICBpZHggPC0gd2hpY2goeCA8IHBvcykNCiAgICAgIGlmIChsZW5ndGgoaWR4KSA+IDApIHhbaWR4XVt3aGljaC5tYXgoc2xvcGVbaWR4XSldIGVsc2UgTkFfcmVhbF8NCiAgICB9IGVsc2UgTkFfcmVhbF8NCg0KICAgIGVvc19zbG9wZSA8LSBpZiAoIWlzLm5hKHBvcykpIHsNCiAgICAgIGlkeCA8LSB3aGljaCh4ID4gcG9zKQ0KICAgICAgaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1pbihzbG9wZVtpZHhdKV0gZWxzZSBOQV9yZWFsXw0KICAgIH0gZWxzZSBOQV9yZWFsXw0KDQogICAgaW50ZWdyYXRpb25faWR4X3Nsb3BlIDwtIHdoaWNoKHggPj0gc29zX3Nsb3BlICYgeCA8PSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlb3Nfc2xvcGUgJiAhaXMubmEocHJlZCkpDQogICAgYXVjX3Nsb3BlIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3Nsb3BlKSA+IDEpIHsNCiAgICAgIHN1bShkaWZmKHhbaW50ZWdyYXRpb25faWR4X3Nsb3BlXSkgKiANCiAgICAgICAgICAgIHpvbzo6cm9sbG1lYW4ocHJlZFtpbnRlZ3JhdGlvbl9pZHhfc2xvcGVdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgVm1pbiBhbnRlcyB5IGRlc3B1w6lzIGRlbCBwaWNvDQogICAgVm1pbl9wcmUgPC0gaWYgKCFpcy5uYShwb3MpKSBtaW4ocHJlZFt4IDw9IHBvc10sIG5hLnJtID0gVFJVRSllbHNlIE5BX3JlYWxfDQogICAgVm1pbl9wb3N0IDwtIGlmICghaXMubmEocG9zKSkgbWluKHByZWRbeCA+PSBwb3NdLCBuYS5ybSA9IFRSVUUpIGVsc2UgTkFfcmVhbF8NCiAgICBWbWF4IDwtIG1heChwcmVkLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgIyBVbWJyYWxlcyByZWxhdGl2b3MNCiAgICBwIDwtIDAuNQ0KICAgIHVfc29zIDwtIGlmICghaXMubmEoVm1pbl9wcmUpKSBWbWluX3ByZSArIHAgKiAoVm1heCAtIFZtaW5fcHJlKSBlbHNlIE5BX3JlYWxfDQogICAgdV9lb3MgPC0gaWYgKCFpcy5uYShWbWluX3Bvc3QpKSBWbWluX3Bvc3QgKyBwICogKFZtYXggLSBWbWluX3Bvc3QpIGVsc2UgTkFfcmVhbF8NCiAgICANCiAgICAjIERPWSBkb25kZSBzZSBjcnV6YW4gbG9zIHVtYnJhbGVzDQogICAgc29zX3RocmVzaG9sZCA8LSBpZiAoIWlzLm5hKHVfc29zKSkgeFt3aGljaChwcmVkID49IHVfc29zKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIGVvc190aHJlc2hvbGQgPC0gaWYgKCFpcy5uYSh1X2VvcykpIHhbcmV2KHdoaWNoKHByZWQgPj0gdV9lb3MpKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgIGludGVncmF0aW9uX2lkeF90aHJlc2hvbGQgPC0gd2hpY2goeCA+PSBzb3NfdGhyZXNob2xkICYgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPD0gZW9zX3RocmVzaG9sZCAmICFpcy5uYShwcmVkKSkNCiAgICBhdWNfdGhyZXNob2xkIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3RocmVzaG9sZCkgPiAxKSB7DQogICAgICBzdW0oZGlmZih4W2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdKSAqIA0KICAgICAgICAgICAgem9vOjpyb2xsbWVhbihwcmVkW2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgMS4gUHJlZGljY2lvbmVzIHBvciBET1kNCiAgICBmaXRzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIERPWSA9IHgsDQogICAgICB2YWx1ZSA9IHByZWQsDQogICAgICBpbmRleCA9IGluZGV4X2NvbA0KICAgICAgKQ0KICAgIA0KICAgICMgMi4gTcOpdHJpY2FzIHJlc3VtZW4NCiAgICBtZXRyaWNzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgc29zX3Nsb3BlID0gc29zX3Nsb3BlLA0KICAgICAgc29zX3RocmVzaG9sZCA9IHNvc190aHJlc2hvbGQsDQogICAgICBwb3MgPSBwb3MsDQogICAgICBlb3Nfc2xvcGUgPSBlb3Nfc2xvcGUsDQogICAgICBlb3NfdGhyZXNob2xkID0gZW9zX3RocmVzaG9sZCwNCiAgICAgIGF1Y19zbG9wZSA9IGF1Y19zbG9wZSwNCiAgICAgIGF1Y190aHJlc2hvbGQgPSBhdWNfdGhyZXNob2xkLA0KICAgICAgVm1pbl9wcmUgPSBWbWluX3ByZSwNCiAgICAgIFZtaW5fcG9zdCA9IFZtaW5fcG9zdCwNCiAgICAgIFZtYXggPSBWbWF4LA0KICAgICAgdV9zb3MgPSB1X3NvcywNCiAgICAgIHVfZW9zID0gdV9lb3MNCiAgICAgICkNCiAgICANCiAgICAjIDMuIFVuaXIgcG9yIFBsb3RPYnNlcnZhdGlvbklELCBpbmRleA0KICAgIGZpbmFsX2RmIDwtIGxlZnRfam9pbihmaXRzX2RmLCBtZXRyaWNzX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJQbG90T2JzZXJ2YXRpb25JRCIsICJpbmRleCIpKQ0KICB9DQogIA0KICAjIFJ1biBpbiBwYXJhbGxlbA0KICByZXN1bHRzIDwtIGZ1dHVyZV9tYXAoaW5kZXhfZGZzLCBwcm9jZXNzX2luZGV4LCAucHJvZ3Jlc3MgPSBUUlVFKQ0KICByZXN1bHRzIDwtIHB1cnJyOjpjb21wYWN0KHJlc3VsdHMpICAjIHJlbW92ZXMgTlVMTHMNCiAgaWYgKGxlbmd0aChyZXN1bHRzKSA9PSAwKSByZXR1cm4odGliYmxlKCkpICAjIG9yIHJldHVybihOVUxMKQ0KICBiaW5kX3Jvd3MocmVzdWx0cykNCn0NCmBgYA0KDQpgYGB7cn0NCiMgIyBPTEQNCiMgY29tcHV0ZV9tZXRyaWNzX21vZGVscyA8LSBmdW5jdGlvbihkZiwgaW5kZXhfY29scyA9IGMoIk5EVkkiLCAiRVZJIiwgIlNBVkkiKSwgDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2l0ZXIgPSAzKSB7DQojICAgcGxhbihtdWx0aXNlc3Npb24pICAjIFNldCB1cCBwYXJhbGxlbCBwcm9jZXNzaW5nDQojIA0KIyAgICMgQ3JlYXRlIGEgbGlzdCBvZiBpbmRleC1zcGVjaWZpYyBkYXRhIGZyYW1lcw0KIyAgIGluZGV4X2RmcyA8LSBsYXBwbHkoaW5kZXhfY29scywgZnVuY3Rpb24oaW5kZXhfY29sKSB7DQojICAgICBsaXN0KGluZGV4X2NvbCA9IGluZGV4X2NvbCwgZGYgPSBkZiAlPiUgDQojICAgICAgICAgICAgDQojICAgICAgICAgICAgc2VsZWN0KERPWSwgUGxvdE9ic2VydmF0aW9uSUQsIGFsbF9vZihpbmRleF9jb2wpKSkNCiMgICB9KQ0KIyANCiMgICAjIERlZmluZSB0aGUgcHJvY2Vzc2luZyBmdW5jdGlvbiBmb3IgZWFjaCBpbmRleA0KIyAgIHByb2Nlc3NfaW5kZXggPC0gZnVuY3Rpb24oaW5kZXhfZGF0YSkgew0KIyAgICAgaW5kZXhfY29sIDwtIGluZGV4X2RhdGEkaW5kZXhfY29sDQojICAgICBkZl9pbmRleCA8LSBpbmRleF9kYXRhJGRmICU+JQ0KIyAgICAgICBmaWx0ZXIoIWlzLm5hKC5kYXRhW1tpbmRleF9jb2xdXSkpICU+JQ0KIyAgICAgICBhcnJhbmdlKERPWSkNCiMgDQojICAgICAjIGlmIChucm93KGRmX2luZGV4KSA8IDExKSByZXR1cm4oTlVMTCkNCiMgDQojICAgICAjIFJlcGxhY2UgZWFybHkvbGF0ZSBET1kgdmFsdWVzDQojICAgICBiYXNlX3ZhbHVlX2Vhcmx5IDwtIG1lYW4oZGZfaW5kZXggJT4lIGZpbHRlcihET1kgPD0gNTApICU+JSANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoaW5kZXhfY29sKSwgbmEucm0gPSBUUlVFKQ0KIyAgICAgYmFzZV92YWx1ZV9sYXRlICA8LSBtZWFuKGRmX2luZGV4ICU+JSBmaWx0ZXIoRE9ZID49IDMxNSkgJT4lIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHVsbChpbmRleF9jb2wpLCBuYS5ybSA9IFRSVUUpDQojIA0KIyAgICAgZGZfaW5kZXggPC0gZGZfaW5kZXggJT4lDQojICAgICAgIG11dGF0ZSghIWluZGV4X2NvbCA6PSBjYXNlX3doZW4oDQojICAgICAgICAgRE9ZIDw9IDUwIH4gYmFzZV92YWx1ZV9lYXJseSwNCiMgICAgICAgICBET1kgPj0gMzE1IH4gYmFzZV92YWx1ZV9sYXRlLA0KIyAgICAgICAgIFRSVUUgfiAuZGF0YVtbaW5kZXhfY29sXV0NCiMgICAgICAgKSkNCiMgDQojICAgICB4IDwtIGRmX2luZGV4JERPWQ0KIyAgICAgeSA8LSBkZl9pbmRleFtbaW5kZXhfY29sXV0NCiMgICAgIGZpdHNfZGYgPC0gdGliYmxlKERPWSA9IHgsIG9ic2VydmVkID0geSkNCiMgDQojICAgICAjIEluaXRpYWwgdW53ZWlnaHRlZCBHQU0NCiMgICAgIGdhbV91bndlaWdodGVkIDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpKQ0KIyAgICAgZml0c19kZiR1bndlaWdodGVkIDwtIHByZWRpY3QoZ2FtX3Vud2VpZ2h0ZWQsIG5ld2RhdGEgPSB0aWJibGUoeCA9IHgpKQ0KIyANCiMgICAgICMgSXRlcmF0aXZlIHJld2VpZ2h0aW5nDQojICAgICB3ZWlnaHRzIDwtIHJlcCgxLCBsZW5ndGgoeSkpDQojICAgICBmb3IgKGkgaW4gMTptYXhfaXRlcikgew0KIyAgICAgICBnYW1fZml0IDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpLCB3ZWlnaHRzID0gd2VpZ2h0cykNCiMgICAgICAgcHJlZCA8LSBwcmVkaWN0KGdhbV9maXQsIG5ld2RhdGEgPSB0aWJibGUoeCA9IHgpKQ0KIyAgICAgICBmaXRzX2RmW1twYXN0ZTAoIml0ZXJfIiwgaSldXSA8LSBwcmVkDQojIA0KIyAgICAgICBpZHhfYmV0d2VlbiA8LSB3aGljaCh4ID4gNTAgJiB4IDwgMzE1ICYgIWlzLm5hKHByZWQpICYgcHJlZCAhPSAwKQ0KIyAgICAgICB3ZWlnaHRzIDwtIHJlcCgxLCBsZW5ndGgoeSkpDQojICAgICAgIHdlaWdodHNbaWR4X2JldHdlZW5dIDwtICh5W2lkeF9iZXR3ZWVuXSAvIChwcmVkW2lkeF9iZXR3ZWVuXSArIDFlLTYpKV40DQojICAgICAgIHdlaWdodHNbd2VpZ2h0cyA+IDEgfCBpcy5uYSh3ZWlnaHRzKV0gPC0gMQ0KIyAgICAgfQ0KIyANCiMgICAgICMgQ29tcHV0ZSBtZXRyaWNzDQojICAgICBtZXRyaWNzX2xpc3QgPC0gbGlzdCgpDQojICAgICBmaXRfdHlwZXMgPC0gYygidW53ZWlnaHRlZCIsIHBhc3RlMCgiaXRlcl8iLCAxOm1heF9pdGVyKSkNCiMgICAgIGZvciAoZml0X3R5cGUgaW4gZml0X3R5cGVzKSB7DQojICAgICAgIHByZWQgPC0gZml0c19kZltbZml0X3R5cGVdXQ0KIyAgICAgICBzbG9wZSA8LSBjKE5BLCBkaWZmKHByZWQpKQ0KIyAgICAgICBpZHggPC0gd2hpY2goeCA+PSA1MCAmIHggPD0gMzE1KQ0KIyAgICAgICBwb3MgPC0gaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1heChwcmVkW2lkeF0pXSBlbHNlIE5BX3JlYWxfDQojIA0KIyAgICAgICBzb3Nfc2xvcGUgPC0gaWYgKCFpcy5uYShwb3MpKSB7DQojICAgICAgICAgaWR4IDwtIHdoaWNoKHggPCBwb3MpDQojICAgICAgICAgaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1heChzbG9wZVtpZHhdKV0gZWxzZSBOQV9yZWFsXw0KIyAgICAgICB9IGVsc2UgTkFfcmVhbF8NCiMgDQojICAgICAgIGVvc19zbG9wZSA8LSBpZiAoIWlzLm5hKHBvcykpIHsNCiMgICAgICAgICBpZHggPC0gd2hpY2goeCA+IHBvcykNCiMgICAgICAgICBpZiAobGVuZ3RoKGlkeCkgPiAwKSB4W2lkeF1bd2hpY2gubWluKHNsb3BlW2lkeF0pXSBlbHNlIE5BX3JlYWxfDQojICAgICAgIH0gZWxzZSBOQV9yZWFsXw0KIyANCiMgICAgICAgaW50ZWdyYXRpb25faWR4X3Nsb3BlIDwtIA0KIyAgICAgICAgIHdoaWNoKHggPj0gc29zX3Nsb3BlICYgeCA8PSBlb3Nfc2xvcGUgJiAhaXMubmEocHJlZCkpDQojICAgICAgIGF1Y19zbG9wZSA8LSBpZiAobGVuZ3RoKGludGVncmF0aW9uX2lkeF9zbG9wZSkgPiAxKSB7DQojICAgICAgICAgc3VtKGRpZmYoeFtpbnRlZ3JhdGlvbl9pZHhfc2xvcGVdKSAqIA0KIyAgICAgICAgICAgICAgIHpvbzo6cm9sbG1lYW4ocHJlZFtpbnRlZ3JhdGlvbl9pZHhfc2xvcGVdLCAyKSkNCiMgICAgICAgICB9IGVsc2UgTkFfcmVhbF8NCiMgICAgICAgDQojICAgICAgICMgRHluYW1pYyB0aHJlc2hvbGQgbWV0aG9kDQojICAgICAgIFZtaW4gPC0gbWluKHByZWQsIG5hLnJtID0gVFJVRSkNCiMgICAgICAgVm1heCA8LSBtYXgocHJlZCwgbmEucm0gPSBUUlVFKQ0KIyAgICAgICBwIDwtIDAuNQ0KIyAgICAgICB1IDwtIFZtaW4gKyBwICogKFZtYXggLSBWbWluKQ0KIyAgICAgICBzb3NfdGhyZXNob2xkIDwtIHhbd2hpY2gocHJlZCA+PSB1KVsxXV0NCiMgICAgICAgZW9zX3RocmVzaG9sZCA8LSB4W3Jldih3aGljaChwcmVkID49IHUpKVsxXV0NCiMgICAgICAgaW50ZWdyYXRpb25faWR4X3RocmVzaG9sZCA8LSANCiMgICAgICAgICB3aGljaCh4ID49IHNvc190aHJlc2hvbGQgJiB4IDw9IGVvc190aHJlc2hvbGQgJiAhaXMubmEocHJlZCkpDQojICAgICAgIGF1Y190aHJlc2hvbGQgPC0gaWYgKGxlbmd0aChpbnRlZ3JhdGlvbl9pZHhfdGhyZXNob2xkKSA+IDEpIHsNCiMgICAgICAgICBzdW0oZGlmZih4W2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdKSAqIA0KIyAgICAgICAgICAgICAgIHpvbzo6cm9sbG1lYW4ocHJlZFtpbnRlZ3JhdGlvbl9pZHhfdGhyZXNob2xkXSwgMikpDQojICAgICAgICAgfSBlbHNlIE5BX3JlYWxfDQojICAgICAgIA0KIyAgICAgICBtZXRyaWNzX2xpc3RbW2ZpdF90eXBlXV0gPC0gdGliYmxlKA0KIyAgICAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiMgICAgICAgICBpbmRleCA9IGluZGV4X2NvbCwNCiMgICAgICAgICBmaXRfdHlwZSA9IGZpdF90eXBlLA0KIyAgICAgICAgIHNvc19zbG9wZSA9IHNvc19zbG9wZSwNCiMgICAgICAgICBzb3NfdGhyZXNob2xkID0gc29zX3RocmVzaG9sZCwNCiMgICAgICAgICBwb3MgPSBwb3MsDQojICAgICAgICAgZW9zX3Nsb3BlID0gZW9zX3Nsb3BlLA0KIyAgICAgICAgIGVvc190aHJlc2hvbGQgPSBlb3NfdGhyZXNob2xkLA0KIyAgICAgICAgIGF1Y19zbG9wZSA9IGF1Y19zbG9wZSwNCiMgICAgICAgICBhdWNfdGhyZXNob2xkID0gYXVjX3RocmVzaG9sZCwNCiMgICAgICAgICBWbWluID0gVm1pbiwNCiMgICAgICAgICBWbWF4ID0gVm1heCwNCiMgICAgICAgICB1ID0gdQ0KIyAgICAgICApDQojICAgICB9DQojIA0KIyAgICAgZml0c19sb25nIDwtIGZpdHNfZGYgJT4lDQojICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gLURPWSwgbmFtZXNfdG8gPSAiZml0X3R5cGUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUNCiMgICAgICAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwgDQojICAgICAgICAgICAgICBpbmRleCA9IGluZGV4X2NvbCkNCiMgDQojICAgICBsaXN0KG1ldHJpY3MgPSBiaW5kX3Jvd3MobWV0cmljc19saXN0KSwgZml0cyA9IGZpdHNfbG9uZykNCiMgICB9DQojIA0KIyAgICMgUnVuIGluIHBhcmFsbGVsDQojICAgcmVzdWx0cyA8LSBmdXR1cmVfbWFwKGluZGV4X2RmcywgcHJvY2Vzc19pbmRleCwgLnByb2dyZXNzID0gVFJVRSkNCiMgDQojICAgIyBDb21iaW5lIHJlc3VsdHMNCiMgICBtZXRyaWNzX2RmIDwtIGJpbmRfcm93cyhtYXAocmVzdWx0cywgIm1ldHJpY3MiKSkNCiMgICBmaXRzX2RmIDwtIGJpbmRfcm93cyhtYXAocmVzdWx0cywgImZpdHMiKSkNCiMgDQojICAgR0FNX2RhdGEgPC0gZml0c19kZiAlPiUNCiMgICAgIGxlZnRfam9pbihtZXRyaWNzX2RmLCBieSA9IGMoIlBsb3RPYnNlcnZhdGlvbklEIiwgImluZGV4IiwgImZpdF90eXBlIikpICU+JQ0KIyAgICAgbXV0YXRlKG1ldGhvZCA9ICJHQU0iKSAlPiUNCiMgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRE9ZLCBtZXRob2QsIGZpdF90eXBlLCBpbmRleCwgdmFsdWUsIHNvc19zbG9wZSwNCiMgICAgICAgICAgICBzb3NfdGhyZXNob2xkLCBwb3MsIGVvc19zbG9wZSwgZW9zX3RocmVzaG9sZCwgYXVjX3Nsb3BlLCANCiMgICAgICAgICAgICBhdWNfdGhyZXNob2xkLCBWbWluLCBWbWF4LCB1KQ0KIyANCiMgICByZXR1cm4oR0FNX2RhdGEpDQojIH0NCmBgYA0KDQpgYGB7cn0NCiMgT0xEDQojIGNvbXB1dGVfbWV0cmljc19tb2RlbHMgPC0gZnVuY3Rpb24oDQojICAgIyBEYXRhIGZyYW1lIGRmIHdpdGggaW5kZXggdmFsdWVzIG92ZXIgdGltZSAoRE9ZKQ0KIyAgIGRmLCANCiMgICAjIE5hbWUgb2YgdGhlIHZlZ2V0YXRpb24gaW5kaWNlcyBjb2x1bW5zIChlLmcuLCAiTkRWSSIsICJFVkkiLCAiU0FWSSIpDQojICAgaW5kZXhfY29scyA9IGMoIk5EVkkiLCAiRVZJIiwgIlNBVkkiKSwNCiMgICAjIE51bWJlciBvZiBpdGVyYXRpb25zIGZvciB0aGUgcmV3ZWlnaHRpbmcgcHJvY2VzcyB0byByZWZpbmUgdGhlIEdBTSBmaXQNCiMgICBtYXhfaXRlciA9IDMNCiMgKSB7DQojICAgIyBJbml0aWFsaXplIGxpc3RzIHRvIHN0b3JlIHJlc3VsdHMNCiMgICBtZXRyaWNzX2xpc3QgPC0gbGlzdCgpDQojICAgZml0c19saXN0IDwtIGxpc3QoKQ0KIyAgIA0KIyAgICMgTG9vcCBvdmVyIGVhY2ggaW5kZXggY29sdW1uDQojICAgZm9yIChpbmRleF9jb2wgaW4gaW5kZXhfY29scykgew0KIyAgICAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgaW5kZXggdmFsdWVzIGFuZCBzb3J0IGRhdGEgYnkgRE9ZDQojICAgICBkZl9pbmRleCA8LSBkZiAlPiUNCiMgICAgICAgZHBseXI6OmZpbHRlcighaXMubmEoLmRhdGFbW2luZGV4X2NvbF1dKSkgJT4lDQojICAgICAgIGFycmFuZ2UoRE9ZKQ0KIyAgICAgDQojICAgICAjIFJlcGxhY2UgdmFsdWVzIGluIERPWSAx4oCTNTAgYW5kIERPWSAzMTXigJNlbmQgd2l0aCBzZXBhcmF0ZSBiYXNlIHZhbHVlcw0KIyAgICAgYmFzZV92YWx1ZV9lYXJseSA8LSBtZWFuKGRmX2luZGV4ICU+JQ0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihET1kgPj0gMSAmIERPWSA8PSA1MCkgJT4lDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKGluZGV4X2NvbCksIG5hLnJtID0gVFJVRSkNCiMgICAgIGJhc2VfdmFsdWVfbGF0ZSAgPC0gbWVhbihkZl9pbmRleCAlPiUNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRE9ZID49IDMxNSkgJT4lDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKGluZGV4X2NvbCksIG5hLnJtID0gVFJVRSkNCiMgICAgIA0KIyAgICAgZGZfaW5kZXggPC0gZGZfaW5kZXggJT4lDQojICAgICAgIG11dGF0ZSghIWluZGV4X2NvbCA6PSBjYXNlX3doZW4oDQojICAgICAgICAgRE9ZIDw9IDUwIH4gYmFzZV92YWx1ZV9lYXJseSwNCiMgICAgICAgICBET1kgPj0gMzE1IH4gYmFzZV92YWx1ZV9sYXRlLA0KIyAgICAgICAgIFRSVUUgfiAuZGF0YVtbaW5kZXhfY29sXV0NCiMgICAgICAgKSkNCiMgICAgIA0KIyAgICAgIyBFeHRyYWN0IHggKERPWSkgYW5kIHkgKGluZGV4KSB2ZWN0b3JzIGZvciBtb2RlbGxpbmcNCiMgICAgIHggPC0gZGZfaW5kZXgkRE9ZDQojICAgICB5IDwtIGRmX2luZGV4W1tpbmRleF9jb2xdXQ0KIyAgICAgDQojICAgICAjIElmIHRoZXJlIGFyZSBmZXdlciB0aGFuIDExIG9ic2VydmF0aW9ucyBvciBhbGwgdmFsdWVzIGFyZSBOQSwgc2tpcA0KIyAgICAgIyBWRVJJRlkgaWYgdGhpcyB2YWx1ZSBpcyBPSyENCiMgICAgIGlmIChsZW5ndGgoeCkgPCAxMSB8fCBhbGwoaXMubmEoeSkpKSB7DQojICAgICAgIG5leHQNCiMgICAgIH0NCiMgICAgIA0KIyAgICAgIyBDcmVhdGUgdGliYmxlIHRvIHN0b3JlIG9yaWdpbmFsIGFuZCBwcmVkaWN0ZWQgaW5kZXggdmFsdWVzDQojICAgICBmaXRzX2RmIDwtIHRpYmJsZShET1kgPSB4LCBvYnNlcnZlZCA9IHkpDQojICAgICANCiMgICAgICMgRml0IGluaXRpYWwgR0FNICh1bndlaWdodGVkKToNCiMgICAgICMgRml0IGEgR0FNIHdpdGggYSB0aGluIHBsYXRlIHNwbGluZSAoYnMgPSAidHAiKSB0byBzbW9vdGggdGhlIGluZGV4IGN1cnZlDQojICAgICBnYW1fdW53ZWlnaHRlZCA8LSBtZ2N2OjpiYW0oeSB+IHMoeCwgYnMgPSAidHAiKSkNCiMgICAgICMgU3RvcmUgdGhlIHByZWRpY3RlZCB2YWx1ZXMgaW4gdGhlIHVud2VpZ2h0ZWQgY29sdW1uDQojICAgICBmaXRzX2RmJHVud2VpZ2h0ZWQgPC0gcHJlZGljdChnYW1fdW53ZWlnaHRlZCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQojICAgICANCiMgICAgICMgSXRlcmF0aXZlIHJld2VpZ2h0ZWQgR0FNIGZpdHRpbmcNCiMgICAgIHdlaWdodHMgPC0gcmVwKDEsIGxlbmd0aCh5KSkgIyBTdGFydCB3aXRoIGVxdWFsIHdlaWdodHMNCiMgICAgIGZvciAoaSBpbiAxOm1heF9pdGVyKSB7DQojICAgICAgICMgVXBkYXRlIHByZWRpY3Rpb24gYW5kIHJlY2FsY3VsYXRlIHdlaWdodHMgdG8gZW1waGFzaXplDQojICAgICAgICMgcG9pbnRzIHdoZXJlIG9ic2VydmVkIGluZGV4IGlzIGhpZ2hlciB0aGFuIHByZWRpY3RlZA0KIyAgICAgICBnYW1fZml0IDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpLCB3ZWlnaHRzID0gd2VpZ2h0cykNCiMgICAgICAgcHJlZCA8LSBwcmVkaWN0KGdhbV9maXQsIG5ld2RhdGEgPSB0aWJibGUoeCA9IHgpKQ0KIyAgICAgICBpZiAoYW55KGlzLm5hKHByZWQpKSkgew0KIyAgICAgICAgIHByaW50KHBhc3RlKCJXYXJuaW5nOiBOQSBwcmVkaWN0aW9ucyBpbiBpdGVyYXRpb24iLCBpLCAiZm9yIiwgaW5kZXhfY29sKSkNCiMgICAgICAgfQ0KIyAgICAgICBmaXRzX2RmW1twYXN0ZTAoIml0ZXJfIiwgaSldXSA8LSBwcmVkDQojICAgICAgIA0KIyAgICAgICAjIEFwcGx5IHdlaWdodGluZyBvbmx5IGJldHdlZW4gRE9ZIDUwIGFuZCAzMTUNCiMgICAgICAgd2VpZ2h0cyA8LSByZXAoMSwgbGVuZ3RoKHkpKQ0KIyAgICAgICBpZHhfYmV0d2VlbiA8LSB3aGljaCh4ID4gNTAgJiB4IDwgMzE1ICYgIWlzLm5hKHByZWQpICYgcHJlZCAhPSAwKQ0KIyAgICAgICB3ZWlnaHRzW2lkeF9iZXR3ZWVuXSA8LSAoeVtpZHhfYmV0d2Vlbl0gLyBwcmVkW2lkeF9iZXR3ZWVuXSleNA0KIyAgICAgICB3ZWlnaHRzW3dlaWdodHMgPiAxXSA8LSAxDQojICAgICAgIHdlaWdodHNbaXMubmEod2VpZ2h0cyldIDwtIDENCiMgICAgIH0NCiMgICAgIA0KIyAgICAgIyBDb21wdXRlIHBoZW5vbG9naWNhbCBtZXRyaWNzIGZvciBlYWNoIGZpdF90eXBlDQojICAgICBmaXRfdHlwZXMgPC0gYygidW53ZWlnaHRlZCIsIHBhc3RlMCgiaXRlcl8iLCAxOm1heF9pdGVyKSkNCiMgICAgIGZvciAoZml0X3R5cGUgaW4gZml0X3R5cGVzKSB7DQojICAgICAgIHByZWQgPC0gZml0c19kZltbZml0X3R5cGVdXQ0KIyAgICAgICANCiMgICAgICAgaWYgKGFsbChpcy5uYShwcmVkKSkpIHsNCiMgICAgICAgICBwcmludChwYXN0ZSgiQWxsIHByZWRpY3Rpb25zIGFyZSBOQSBmb3IiLCBmaXRfdHlwZSwgIm9uIiwgaW5kZXhfY29sKSkNCiMgICAgICAgfQ0KIyAgICAgICANCiMgICAgICAgc2xvcGUgPC0gYyhOQSwgZGlmZihwcmVkKSkNCiMgICAgICAgDQojICAgICAgIHBvcyA8LSB7DQojICAgICAgICAgaWR4IDwtIHdoaWNoKHggPj0gNTAgJiB4IDw9IDMxNSkNCiMgICAgICAgICBpZiAobGVuZ3RoKGlkeCkgPiAwKSB4W2lkeF1bd2hpY2gubWF4KHByZWRbaWR4XSldIGVsc2UgTkFfcmVhbF8NCiMgICAgICAgfQ0KIyAgICAgICANCiMgICAgICAgaWYgKGlzLm5hKHBvcykpIHsNCiMgICAgICAgICBwcmludChwYXN0ZSgiUE9TIGlzIE5BIGZvciIsIGZpdF90eXBlLCAib24iLCBpbmRleF9jb2wpKQ0KIyAgICAgICB9DQojICAgICAgIA0KIyAgICAgICBzb3MgPC0gew0KIyAgICAgICAgIGlkeCA8LSB3aGljaCh4IDwgcG9zKQ0KIyAgICAgICAgIGlmIChsZW5ndGgoaWR4KSA+IDApIHsNCiMgICAgICAgICAgIHN1Yl94IDwtIHhbaWR4XQ0KIyAgICAgICAgICAgc3ViX3Nsb3BlIDwtIHNsb3BlW2lkeF0NCiMgICAgICAgICAgIHN1Yl94W3doaWNoLm1heChzdWJfc2xvcGUpXQ0KIyAgICAgICAgIH0gZWxzZSBOQV9yZWFsXw0KIyAgICAgICB9DQojICAgICAgIA0KIyAgICAgICBlb3MgPC0gew0KIyAgICAgICAgIGlkeCA8LSB3aGljaCh4ID4gcG9zKQ0KIyAgICAgICAgIGlmIChsZW5ndGgoaWR4KSA+IDApIHsNCiMgICAgICAgICAgIHN1Yl94IDwtIHhbaWR4XQ0KIyAgICAgICAgICAgc3ViX3Nsb3BlIDwtIHNsb3BlW2lkeF0NCiMgICAgICAgICAgIHN1Yl94W3doaWNoLm1pbihzdWJfc2xvcGUpXQ0KIyAgICAgICAgIH0gZWxzZSBOQV9yZWFsXw0KIyAgICAgICB9DQojICAgICAgIA0KIyAgICAgICAjIENvbXB1dGUgdGltZS1pbnRlZ3JhdGVkIGluZGV4IChBVUMpIGJldHdlZW4gU09TIGFuZCBFT1MNCiMgICAgICAgaW50ZWdyYXRpb25faWR4IDwtIHdoaWNoKHggPj0gc29zICYgeCA8PSBlb3MgJiAhaXMubmEocHJlZCkpDQojICAgICAgIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4KSA+IDEpIHsNCiMgICAgICAgICBhdWMgPC0gc3VtKGRpZmYoeFtpbnRlZ3JhdGlvbl9pZHhdKSAqIA0KIyAgICAgICAgICAgICAgICAgICAgICB6b286OnJvbGxtZWFuKHByZWRbaW50ZWdyYXRpb25faWR4XSwgMikpDQojICAgICAgIH0gZWxzZSB7DQojICAgICAgICAgYXVjIDwtIE5BX3JlYWxfDQojICAgICAgIH0NCiMgICAgICAgDQojICAgICAgIG1ldHJpY3NfbGlzdFtbcGFzdGUoaW5kZXhfY29sLCBmaXRfdHlwZSwgc2VwID0gIl8iKV1dIDwtIHRpYmJsZSgNCiMgICAgICAgICBQbG90T2JzZXJ2YXRpb25JRCA9IHVuaXF1ZShkZiRQbG90T2JzZXJ2YXRpb25JRCksDQojICAgICAgICAgaW5kZXggPSBpbmRleF9jb2wsDQojICAgICAgICAgZml0X3R5cGUgPSBmaXRfdHlwZSwNCiMgICAgICAgICBzb3MgPSBzb3MsDQojICAgICAgICAgcG9zID0gcG9zLA0KIyAgICAgICAgIGVvcyA9IGVvcywNCiMgICAgICAgICBhdWMgPSBhdWMNCiMgICAgICAgKQ0KIyAgICAgfQ0KIyAgICAgDQojICAgICAjIFN0b3JlIGZpdHMgaW4gbG9uZyBmb3JtYXQNCiMgICAgIGZpdHNfbG9uZyA8LSBmaXRzX2RmICU+JQ0KIyAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IC1ET1ksIG5hbWVzX3RvID0gImZpdF90eXBlIiwgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQojICAgICAgIG11dGF0ZSgNCiMgICAgICAgICBQbG90T2JzZXJ2YXRpb25JRCA9IHVuaXF1ZShkZiRQbG90T2JzZXJ2YXRpb25JRCksDQojICAgICAgICAgaW5kZXggPSBpbmRleF9jb2wNCiMgICAgICAgKQ0KIyAgICAgDQojICAgICBmaXRzX2xpc3RbW2luZGV4X2NvbF1dIDwtIGZpdHNfbG9uZw0KIyAgIH0NCiMgICANCiMgICAjIEZhbGxiYWNrIGluIGNhc2Ugbm8gbWV0cmljcyB3ZXJlIGNvbXB1dGVkDQojICAgaWYgKGxlbmd0aChtZXRyaWNzX2xpc3QpID09IDApIHsNCiMgICAgIHByaW50KHBhc3RlKCJObyBtZXRyaWNzIGNvbXB1dGVkIGZvciBQbG90T2JzZXJ2YXRpb25JRDoiLA0KIyAgICAgICAgICAgICAgICAgdW5pcXVlKGRmJFBsb3RPYnNlcnZhdGlvbklEKSkpDQojICAgICByZXR1cm4odGliYmxlKCkpDQojICAgfQ0KIyAgIA0KIyAgICMgQ29tYmluZSBtZXRyaWNzIGFuZCBmaXRzIGludG8gYSBzaW5nbGUgR0FNX2RhdGEgdGliYmxlDQojICAgbWV0cmljc19kZiA8LSBiaW5kX3Jvd3MobWV0cmljc19saXN0WyFzYXBwbHkobWV0cmljc19saXN0LCBpcy5udWxsKV0pDQojICAgZml0c19kZiA8LSBiaW5kX3Jvd3MoZml0c19saXN0KQ0KIyAgIA0KIyAgIEdBTV9kYXRhIDwtIGZpdHNfZGYgJT4lDQojICAgICBsZWZ0X2pvaW4obWV0cmljc19kZiwgYnkgPSBjKCJQbG90T2JzZXJ2YXRpb25JRCIsICJpbmRleCIsICJmaXRfdHlwZSIpKSAlPiUNCiMgICAgIG11dGF0ZShtZXRob2QgPSAiR0FNIikgJT4lDQojICAgICAjIFNwZWNpZnkgY29sdW1uIG9yZGVyDQojICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIERPWSwgbWV0aG9kLCBmaXRfdHlwZSwgaW5kZXgsIHZhbHVlLA0KIyAgICAgICAgICAgIHNvcywgcG9zLCBlb3MsIGF1YykNCiMgICANCiMgICAjIFJldHVybiB0aGUgY29tYmluZWQgdGliYmxlDQojICAgcmV0dXJuKEdBTV9kYXRhKQ0KIyB9DQpgYGANCg0KIyMgQ2FsY3VsYXRpb24NCg0KQXBwbHkgdGhlIGZ1bmN0aW9uIHdpdGggYmF0Y2ggcHJvY2Vzc2luZw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KcGxhbihtdWx0aXNlc3Npb24sIHdvcmtlcnMgPSBhdmFpbGFibGVDb3JlcygpIC0gMSkNCg0KaWRzIDwtIHVuaXF1ZShkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMkUGxvdE9ic2VydmF0aW9uSUQpDQpiYXRjaGVzIDwtIHNwbGl0KGlkcywgY2VpbGluZyhzZXFfYWxvbmcoaWRzKSAvIDUwKSkgICMgYmF0Y2hlcyBvZiA1MA0KDQpzdGFydF90b3RhbCA8LSBTeXMudGltZSgpDQoNCkdBTV9kYXRhIDwtIG1hcF9kZnIoc2VxX2Fsb25nKGJhdGNoZXMpLCBmdW5jdGlvbihpKSB7DQogIGJhdGNoX2lkcyA8LSBiYXRjaGVzW1tpXV0NCiAgdG90YWxfYmF0Y2hlcyA8LSBsZW5ndGgoYmF0Y2hlcykNCiAgYmF0Y2hfZmlsZSA8LSBmaWxlLnBhdGgoIm9iamVjdHMvR0FNX2JhdGNoZXMiLCBwYXN0ZTAoImJhdGNoXyIsIGksICIucmRzIikpDQoNCiAgaWYgKGZpbGUuZXhpc3RzKGJhdGNoX2ZpbGUpKSB7DQogICAgbWVzc2FnZSgi4pyFIEJhdGNoICAiLCBpLCAiIG9mICIsIHRvdGFsX2JhdGNoZXMsIA0KICAgICAgICAgICAgIiBhbHJlYWR5IHByb2Nlc3NlZC4gTG9hZGluZyBmcm9tIGZpbGUuIikNCiAgICByZXR1cm4ocmVhZFJEUyhiYXRjaF9maWxlKSkNCiAgfQ0KDQogIG1lc3NhZ2UoIvCflIQgUHJvY2Vzc2luZyBiYXRjaCAgIiwgaSwgIiBvZiAiLCB0b3RhbF9iYXRjaGVzLCAiIHdpdGggIiwNCiAgICAgICAgICBsZW5ndGgoYmF0Y2hfaWRzKSwgIiBJRHMuLi4iKQ0KDQogIHN0YXJ0X2JhdGNoIDwtIFN5cy50aW1lKCkNCg0KICByZXN1bHQgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgIGZpbHRlcihQbG90T2JzZXJ2YXRpb25JRCAlaW4lIGJhdGNoX2lkcykgJT4lDQogICAgZ3JvdXBfc3BsaXQoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICAgIHNldF9uYW1lcyhtYXBfY2hyKC4sIH4gYXMuY2hhcmFjdGVyKHVuaXF1ZSgueCRQbG90T2JzZXJ2YXRpb25JRCkpKSkgJT4lDQogICAgZnV0dXJlX21hcF9kZnIofiBjb21wdXRlX21ldHJpY3NfbW9kZWxzKGRmID0gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhfY29scyA9IGMoIk5EVkkiLCAiRVZJIiwgIlNBVkkiKSksDQogICAgICAgICAgICAgICAgICAgLnByb2dyZXNzID0gVFJVRSkNCg0KICBlbmRfYmF0Y2ggPC0gU3lzLnRpbWUoKQ0KICBkdXJhdGlvbiA8LSByb3VuZChkaWZmdGltZShlbmRfYmF0Y2gsIHN0YXJ0X2JhdGNoLCB1bml0cyA9ICJtaW5zIiksIDIpDQogIG1lc3NhZ2UoIuKPse+4jyBCYXRjaCB0aW1lICIsIGksICI6ICIsIGR1cmF0aW9uLCAiIG1pbnV0ZXMiKQ0KDQogIG1lc3NhZ2UoIvCfkr4gU2F2aW5nIGJhdGNoICIsIGksICIgdG8gZmlsZS4uLiIpDQogIHNhdmVSRFMocmVzdWx0LCBiYXRjaF9maWxlKQ0KICBtZXNzYWdlKCLinIUgQmF0Y2ggIiwgaSwgIiBzYXZlZC4iKSANCg0KICByZXN1bHQNCn0pDQoNCmVuZF90b3RhbCA8LSBTeXMudGltZSgpDQp0b3RhbF90aW1lIDwtIHJvdW5kKGRpZmZ0aW1lKGVuZF90b3RhbCwgc3RhcnRfdG90YWwsIHVuaXRzID0gIm1pbnMiKSwgMikNCm1lc3NhZ2UoIuKPse+4jyBUb3RhbCB0aW1lOiAiLCB0b3RhbF90aW1lLCAiIG1pbnV0ZXMiKQ0KYGBgDQoNCmBgYHtyfQ0KcGxhbihzZXF1ZW50aWFsKQ0KYGBgDQoNCiMjIFNhdmUNCg0KTG9vazoNCg0KYGBge3J9DQpHQU1fZGF0YQ0KYGBgDQoNClNhdmUgYXMgYW4gb2JqZWN0Og0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShHQU1fZGF0YSwgZmlsZSA9ICJvYmplY3RzL0dBTV9kYXRhX1MyLlJkYXRhIikNCmBgYA0KDQojIyBFeHRyYWN0IGF2ZXJhZ2UgdmFsdWVzIG9mIGluZGljZXMgcGVyIG1vbnRoDQoNCmBgYHtyfQ0KZXh0cmFjdF9tb250aGx5X2F2Z19pbmRpY2VzIDwtIGZ1bmN0aW9uKA0KICAgIEdBTV9kYXRhLCANCiAgICBtb250aGx5X2RveXMgPSBsaXN0KCIwMSIgPSAxOjMxLCAiMDIiID0gMzI6NTksICIwMyIgPSA2MDo5MCwgIjA0IiA9IDkxOjEyMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAiMDUiID0gMTIxOjE1MSwgIjA2IiA9IDE1MjoxODEsICIwNyIgPSAxODI6MjEyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICIwOCIgPSAyMTM6MjQzLCAiMDkiID0gMjQ0OjI3MywgIjEwIiA9IDI3NDozMDQsDQogICAgICAgICAgICAgICAgICAgICAgICAiMTEiID0gMzA1OjMzNCwgIjEyIiA9IDMzNTozNjUpKSB7DQogIEdBTV9kYXRhICU+JQ0KICAgIG11dGF0ZShtb250aCA9IHB1cnJyOjptYXBfY2hyKERPWSwgZnVuY3Rpb24oZG95KSB7DQogICAgICBtb250aF9uYW1lIDwtIG5hbWVzKG1vbnRobHlfZG95cylbc2FwcGx5KG1vbnRobHlfZG95cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHIpIGRveSAlaW4lIHIpXQ0KICAgICAgaWYgKGxlbmd0aChtb250aF9uYW1lKSA+IDApIG1vbnRoX25hbWUgZWxzZSBOQV9jaGFyYWN0ZXJfDQogICAgfSkpICU+JQ0KICAgIGZpbHRlcighaXMubmEobW9udGgpKSAlPiUNCiAgICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIG1vbnRoKSAlPiUNCiAgICBzdW1tYXJpc2UoYXZnX3ZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogICAgbXV0YXRlKGF2Z192YWx1ZSA9IGlmZWxzZShpcy5pbmZpbml0ZShhdmdfdmFsdWUpLCBOQSwgYXZnX3ZhbHVlKSkgJT4lDQogICAgYXJyYW5nZShQbG90T2JzZXJ2YXRpb25JRCwgbWF0Y2gobW9udGgsIG5hbWVzKG1vbnRobHlfZG95cykpKSAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9udGgsIHZhbHVlc19mcm9tID0gYXZnX3ZhbHVlLA0KICAgICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJhdmdfdmFsdWVfIikNCn0NCmBgYA0KDQpgYGB7cn0NCm1vbnRobHlfYXZnX2luZGljZXMgPC0gZXh0cmFjdF9tb250aGx5X2F2Z19pbmRpY2VzKEdBTV9kYXRhKQ0KYGBgDQoNClNhdmUgYXMgYW4gb2JqZWN0Og0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShtb250aGx5X2F2Z19pbmRpY2VzLCBmaWxlID0gIm9iamVjdHMvbW9udGhseV9hdmdfaW5kaWNlc19TMi5SZGF0YSIpDQpgYGANCg0KIyMgQXNzZXNzIHRpbWUgc2VyaWVzIHF1YWxpdHkNCg0KRm9yIHRoZSB0aW1lIHNlcmllcyB0byBiZSBhY2NlcHRhYmxlLCBpdCBzaG91bGQgaGF2ZSBhIHJlYXNvbmFibGUgbnVtYmVyIG9mIHRpbWUgcG9pbnRzLCBhbmQgdGhlc2UgcG9pbnRzIHNob3VsZCBiZSBkaXN0cmlidXRlZCBhbG9uZyBhbG1vc3QgYWxsIG1vbnRocyAoY291bGQgYmUgb2sgdG8gbWlzcyB0aGUgd2ludGVyIG1vbnRocykuDQoNCkluIEdBTSBkYXRhLCBjaGVjayBob3cgbWFueSB0aW1lIHBvaW50cyBhcmUgdGhlcmUgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQsIGhvdyBtYW55IG1vbnRocywgYW5kIHdoaWNoIG1vbnRocyBhcmUgbWlzc2luZy4NCg0KYGBge3J9DQp0c19xdWFsaXR5IDwtIEdBTV9kYXRhICU+JQ0KICAjIEZpbHRlciBvbmx5IE5EVkkgKGFsbCBpbmRpY2VzIHdpbGwgaGF2ZSB0aGUgc2FtZSB0aW1lIHBvaW50cykNCiAgZHBseXI6OmZpbHRlcihpbmRleCA9PSAiTkRWSSIpICU+JQ0KICAjIEdldCBtb250aCBmcm9tIERPWQ0KICBtdXRhdGUobW9udGggPSBtb250aCh5bWQoIjIwMjAtMDEtMDEiKSArIGRheXMoRE9ZIC0gMSkpKSAlPiUNCiAgIyBGb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRA0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogICMgR2V0IHRoZSBudW1iZXIgb2YgdGltZSBwb2ludHMgKGRheXMpIGFuZCB0aGUgbnVtYmVyIG9mIG1vbnRocw0KICBzdW1tYXJpc2UoDQogICAgbl9kYXlzID0gbl9kaXN0aW5jdChET1kpLA0KICAgIG5fbW9udGhzID0gbl9kaXN0aW5jdChtb250aCksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApICU+JQ0KICBsZWZ0X2pvaW4oR0FNX2RhdGEgJT4lDQogICAgICAgICAgICAgICMgRmlsdGVyIG9ubHkgTkRWSQ0KICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKGluZGV4ID09ICJORFZJIikgJT4lDQogICAgICAgICAgICAgICMgR2V0IG1vbnRoIGZyb20gRE9ZDQogICAgICAgICAgICAgIG11dGF0ZShtb250aCA9IG1vbnRoKHltZCgiMjAyMC0wMS0wMSIpICsgZGF5cyhET1kgLSAxKSkpICU+JQ0KICAgICAgICAgICAgICAjIEdldCB1bmlxdWUgdmFsdWVzIG9mIFBsb3RPYnNlcnZhdGlvbklEIGFuZCBtb250aA0KICAgICAgICAgICAgICBkaXN0aW5jdChQbG90T2JzZXJ2YXRpb25JRCwgbW9udGgpICU+JQ0KICAgICAgICAgICAgICAjIEFkZCAxIGFzIHZhbHVlDQogICAgICAgICAgICAgIG11dGF0ZSh2YWx1ZSA9IDEpICU+JQ0KICAgICAgICAgICAgICAjIFJlc2hhcGUgdG8gd2lkZSBmb3JtYXQgYW5kIGFkZCB6ZXJvcyB3aGVuIG1vbnRoIGlzIG1pc3NpbmcNCiAgICAgICAgICAgICAgcGl2b3Rfd2lkZXIoDQogICAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IG1vbnRoLA0KICAgICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJtb250aCIsDQogICAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSB2YWx1ZSwNCiAgICAgICAgICAgICAgICB2YWx1ZXNfZmlsbCA9IDApLA0KICAgICAgICAgICAgYnkgPSAiUGxvdE9ic2VydmF0aW9uSUQiKQ0KYGBgDQoNCkhpc3RvZ3JhbXMgdGltZSBwb2ludHMgYW5kIG4gbW9udGhzOg0KDQpgYGB7cn0NCmdncGxvdCh0c19xdWFsaXR5LCBhZXMoeCA9IG5fZGF5cykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIndoaXRlIikgKw0KICB4bGFiKCJOdW1iZXIgb2YgdGltZSBwb2ludHMgKGRheXMpIGluIHRoZSBTMiB0aW1lIHNlcmllcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3Bsb3QodHNfcXVhbGl0eSwgYWVzKHggPSBuX21vbnRocykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIndoaXRlIikgKw0KICB4bGFiKCJOdW1iZXIgb2YgbW9udGhzIGluIHRoZSBTMiB0aW1lIHNlcmllcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KQ291bnQgaG93IG1hbnkgUGxvdE9ic2VydmF0aW9uSURzIGhhdmUgbWlzc2luZyBkYXRhICh2YWx1ZSAwKSBmb3IgZWFjaCBtb250aDoNCg0KYGBge3J9DQpvYnNfbWlzc2luZ19tb250aCA8LSB0c19xdWFsaXR5ICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHN0YXJ0c193aXRoKCJtb250aCIpLCB+IHN1bSgueCA9PSAwKSkpICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAibW9udGgiLCB2YWx1ZXNfdG8gPSAibm9ic19taXNzaW5nIikNCg0KZ2dwbG90KG9ic19taXNzaW5nX21vbnRoICU+JQ0KICAgICAgICAgbXV0YXRlKG1vbnRoID0gZmFjdG9yKG1vbnRoLCBsZXZlbHMgPSBwYXN0ZTAoIm1vbnRoIiwgMToxMikpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG5vYnNfbWlzc2luZykpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgeWxhYigiTnVtYmVyIG9mIFBsb3RPYnNlcnZhdGlvbklEIHdpdGggbWlzc2luZyBkYXRhIikgKw0KICBnZ3RpdGxlKCJNaXNzaW5nIGRhdGEgaW4gUzIgdGltZSBzZXJpZXMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCkFkZCBxdWFsaXR5IGZsYWc6DQoNCmBgYHtyfQ0KdHNfcXVhbGl0eV9mbGFnIDwtIHRzX3F1YWxpdHkgJT4lDQogIHJvd3dpc2UoKSAlPiUNCiAgbXV0YXRlKA0KICAgICMgIElmIDIgY29uc2VjdXRpdmUgbW9udGhzIG9mIHRoZSBwZXJpb2QgTWFyY2gtT2N0b2JlciBhcmUgbWlzc2luZw0KICAgICMgcXVhbGl0eV9mbGFnID0gMA0KICAgIHF1YWxpdHlfZmxhZyA9IHsNCiAgICAgIG1vbnRocyA8LSBjX2Fjcm9zcyhtb250aDM6bW9udGgxMCkNCiAgICAgIGlmIChhbnkobW9udGhzWy1sZW5ndGgobW9udGhzKV0gPT0gMCAmIG1vbnRoc1stMV0gPT0gMCkpIDAgZWxzZSAxDQogICAgfQ0KICApICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQpgYGB7cn0NCnRzX3F1YWxpdHlfZmxhZyAlPiUgY291bnQocXVhbGl0eV9mbGFnKQ0KYGBgDQoNCiMjIEJveHBsb3QgY29tcGFyaW5nIG1vbWVudHMgZm9yIGRpZmZlcmVudCBpbmRpY2VzDQoNCmBgYHtyfQ0KR0FNX2RhdGEgJT4lIA0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBzb3Nfc2xvcGUsIHNvc190aHJlc2hvbGQsIHBvcywgZW9zX3Nsb3BlLA0KICAgICAgICAgZW9zX3RocmVzaG9sZCkgJT4lIGRpc3RpbmN0KCkgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzb3Nfc2xvcGUsIHNvc190aHJlc2hvbGQsIHBvcywgZW9zX3Nsb3BlLCBlb3NfdGhyZXNob2xkKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm1vbWVudCIsIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBtb21lbnQsIHkgPSB2YWx1ZSwgZmlsbCA9IGluZGV4KSkgKyBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIFBsb3QgZml0IGFuZCBtb21lbnRzIGZvciBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQoNCiMjIyBRdWFsaXR5ID0gMQ0KDQpgYGB7cn0NCiMgR2V0IHVuaXF1ZSBJRHMgd2l0aCBxdWFsaXR5X2ZsYWcgPT0gMQ0KaWRzX3ExIDwtIHRzX3F1YWxpdHlfZmxhZyAlPiUNCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMSkgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGRyb3BsZXZlbHMoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgcHVsbChQbG90T2JzZXJ2YXRpb25JRCkNCkdBTV9kYXRhX2lkc19xMSA8LSBHQU1fZGF0YSAlPiUNCiAgIyBKb2luIHRvIGdldCBiaW9nZW8gYW5kIHVuaXQNCiAgbGVmdF9qb2luKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBiaW9nZW8sIHVuaXQpICU+JQ0KICAgICAgICAgICAgICBkaXN0aW5jdCgpKSAlPiUNCiAgIyBKb2luIHRvIGdldCBFVU5JUyBpbmZvDQogICBsZWZ0X2pvaW4oZGJfRXVyb3BhX2FsbG9icyAlPiUNCiAgICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgICAgIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjcikpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IG9yaWdpbmFsIHZhbHVlcyBvZiBpbmRpY2VzDQogIGxlZnRfam9pbihkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRE9ZLCBORFZJLCBFVkksIFNBVkkpICU+JQ0KICAgICAgICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoTkRWSSwgRVZJLCBTQVZJKSwgbmFtZXNfdG8gPSAiaW5kZXgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZV9vcmlnIikpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IHRzX3F1YWxpdHkgZGF0YQ0KICBsZWZ0X2pvaW4odHNfcXVhbGl0eV9mbGFnICU+JSBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIHF1YWxpdHlfZmxhZykpICU+JQ0KICAjIEtlZXAgb25seSB0aG9zZSB3aXRoIHF1YWxpdHlfZmxhZyA9PSAxDQogIGRwbHlyOjpmaWx0ZXIocXVhbGl0eV9mbGFnID09IDEpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMxIDwtIGlkc19xMQ0KDQojIENyZWF0ZSBhbmQgc3RvcmUgcGxvdHMgaW4gYSBsaXN0DQp0c19wbG90c19xMSA8LSBtYXAodW5pcXVlX2lkczEsIGZ1bmN0aW9uKGlkKSB7DQogIHBsb3RfZGF0YSA8LSBHQU1fZGF0YV9pZHNfcTEgJT4lDQogICAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gYXMuY2hhcmFjdGVyKFBsb3RPYnNlcnZhdGlvbklEKSkgJT4lDQogICAgZHBseXI6OmZpbHRlcihQbG90T2JzZXJ2YXRpb25JRCA9PSBpZCkgDQogIA0KICAjIEV4dHJhY3QgbWV0YWRhdGEgZm9yIHRpdGxlDQogIG1ldGFkYXRhIDwtIHBsb3RfZGF0YSAlPiUNCiAgICBzZWxlY3QoYmlvZ2VvLCB1bml0LCBFVU5JU2FfMSwgRVVOSVNhXzIsIHF1YWxpdHlfZmxhZykgJT4lDQogICAgZGlzdGluY3QoKQ0KICANCiAgZ2dwbG90KCkgKw0KICAgICMgUmF3IGRhdGEgcG9pbnRzDQogICAgZ2VvbV9wb2ludChkYXRhID0gcGxvdF9kYXRhLGFlcyh4ID0gRE9ZLCB5ID0gdmFsdWVfb3JpZyksIGFscGhhID0gMC41KSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBwbG90X2RhdGEsIGFlcyh4ID0gRE9ZLCB5ID0gdmFsdWUpLCANCiAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBwbG90X2RhdGEgJT4lIGRpc3RpbmN0KGluZGV4LCBzb3Nfc2xvcGUpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBzb3Nfc2xvcGUsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHNvc190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBzb3NfdGhyZXNob2xkLCBncm91cCA9IGluZGV4KSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gImRhcmtncmVlbiIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBwbG90X2RhdGEgJT4lIGRpc3RpbmN0KGluZGV4LCBwb3MpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBwb3MsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZG90dGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBwbG90X2RhdGEgJT4lIGRpc3RpbmN0KGluZGV4LCBlb3Nfc2xvcGUpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBlb3Nfc2xvcGUsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIGVvc190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBlb3NfdGhyZXNob2xkLCBncm91cCA9IGluZGV4KSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gImRhcmtncmVlbiIpICsNCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKGluZGV4KSkgKw0KICAgIGxhYnMoDQogICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntpZH0gfCB7bWV0YWRhdGEkYmlvZ2VvfXttZXRhZGF0YSR1bml0fSB8IHttZXRhZGF0YSRFVU5JU2FfMX0gfCB7bWV0YWRhdGEkRVVOSVNhXzJ9IHwgUXVhbGl0eToge21ldGFkYXRhJHF1YWxpdHlfZmxhZ30iKSwNCiAgICAgIHggPSAiRGF5IG9mIFllYXIiLA0KICAgICAgeSA9ICJJbmRleCBWYWx1ZSINCiAgICApICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCn0pDQoNCiMgTmFtZSB0aGUgbGlzdCBieSBQbG90T2JzZXJ2YXRpb25JRA0KbmFtZXModHNfcGxvdHNfcTEpIDwtIHVuaXF1ZV9pZHMxDQoNCiMgRGlzcGxheSB0aGUgZmlyc3QgcGxvdA0KdHNfcGxvdHNfcTFbMV0NCmBgYA0KDQpTYXZlIGVhY2ggcGxvdCB0byBhIGZpbGU6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp3YWxrMih0c19wbG90c19xMSwgc2VxX2Fsb25nKHRzX3Bsb3RzX3ExKSwgfiBnZ3NhdmUoDQogIGZpbGVuYW1lID0gcGFzdGUwKCJvdXRwdXQvZmlndXJlcy9waGVub2xvZ3kvdHNfcTEvdHNfcGxvdHNfcTEiLCAueSwgIi5qcGVnIiksDQogIHBsb3QgPSAueCwNCiAgd2lkdGggPSA4LA0KICBoZWlnaHQgPSA1DQopKQ0KYGBgDQoNCiMjIyBRdWFsaXR5ID0gMA0KDQpgYGB7cn0NCiMgR2V0IHVuaXF1ZSBJRHMgd2l0aCBxdWFsaXR5X2ZsYWcgPT0gMA0KaWRzX3EwIDwtIHRzX3F1YWxpdHlfZmxhZyAlPiUNCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMCkgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGRyb3BsZXZlbHMoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgcHVsbChQbG90T2JzZXJ2YXRpb25JRCkNCkdBTV9kYXRhX2lkc19xMCA8LSBHQU1fZGF0YSAlPiUNCiAgIyBKb2luIHRvIGdldCBiaW9nZW8gYW5kIHVuaXQNCiAgbGVmdF9qb2luKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBiaW9nZW8sIHVuaXQpICU+JQ0KICAgICAgICAgICAgICBkaXN0aW5jdCgpKSAlPiUNCiAgIyBKb2luIHRvIGdldCBFVU5JUyBpbmZvDQogICBsZWZ0X2pvaW4oZGJfRXVyb3BhX2FsbG9icyAlPiUNCiAgICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLA0KICAgICAgICAgICAgICAgICAgICAgIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjcikpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IG9yaWdpbmFsIHZhbHVlcyBvZiBpbmRpY2VzDQogIGxlZnRfam9pbihkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgRE9ZLCBORFZJLCBFVkksIFNBVkkpICU+JQ0KICAgICAgICAgICAgICBwaXZvdF9sb25nZXIoY29scyA9IGMoTkRWSSwgRVZJLCBTQVZJKSwgbmFtZXNfdG8gPSAiaW5kZXgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZV9vcmlnIikpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IHRzX3F1YWxpdHkgZGF0YQ0KICBsZWZ0X2pvaW4odHNfcXVhbGl0eV9mbGFnICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIG5fbW9udGhzLCBxdWFsaXR5X2ZsYWcpKSAlPiUNCiAgIyBLZWVwIG9ubHkgdGhvc2Ugd2l0aCBxdWFsaXR5X2ZsYWcgPT0gMA0KICBkcGx5cjo6ZmlsdGVyKHF1YWxpdHlfZmxhZyA9PSAwKQ0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojIEdldCB1bmlxdWUgUGxvdE9ic2VydmF0aW9uSURzDQp1bmlxdWVfaWRzMCA8LSBpZHNfcTANCg0KIyBDcmVhdGUgYW5kIHN0b3JlIHBsb3RzIGluIGEgbGlzdA0KdHNfcGxvdHNfcTAgPC0gbWFwKHVuaXF1ZV9pZHMwLCBmdW5jdGlvbihpZCkgew0KICBwbG90X2RhdGEgPC0gR0FNX2RhdGFfaWRzX3EwICU+JQ0KICAgIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGFzLmNoYXJhY3RlcihQbG90T2JzZXJ2YXRpb25JRCkpICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoUGxvdE9ic2VydmF0aW9uSUQgPT0gaWQpIA0KICANCiAgIyBFeHRyYWN0IG1ldGFkYXRhIGZvciB0aXRsZQ0KICBtZXRhZGF0YSA8LSBwbG90X2RhdGEgJT4lDQogICAgc2VsZWN0KGJpb2dlbywgdW5pdCwgRVVOSVNhXzEsIEVVTklTYV8yLCBxdWFsaXR5X2ZsYWcpICU+JQ0KICAgIGRpc3RpbmN0KCkNCiAgDQogIGdncGxvdCgpICsNCiAgICAjIFJhdyBkYXRhIHBvaW50cw0KICAgIGdlb21fcG9pbnQoZGF0YSA9IHBsb3RfZGF0YSxhZXMoeCA9IERPWSwgeSA9IHZhbHVlX29yaWcpLCBhbHBoYSA9IDAuNSkgKw0KICAgIGdlb21fbGluZShkYXRhID0gcGxvdF9kYXRhLCBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwgDQogICAgICAgICAgICAgIHNpemUgPSAwLjUsIGNvbG9yID0gImJsdWUiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgc29zX3Nsb3BlKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gc29zX3Nsb3BlLCBncm91cCA9IGluZGV4KSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBwbG90X2RhdGEgJT4lIGRpc3RpbmN0KGluZGV4LCBzb3NfdGhyZXNob2xkKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gc29zX3RocmVzaG9sZCwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgcG9zKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gcG9zLCBncm91cCA9IGluZGV4KSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRvdHRlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gImJsdWUiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgZW9zX3Nsb3BlKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gZW9zX3Nsb3BlLCBncm91cCA9IGluZGV4KSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBwbG90X2RhdGEgJT4lIGRpc3RpbmN0KGluZGV4LCBlb3NfdGhyZXNob2xkKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gZW9zX3RocmVzaG9sZCwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArDQogICAgZmFjZXRfZ3JpZChjb2xzID0gdmFycyhpbmRleCkpICsNCiAgICBsYWJzKA0KICAgICAgdGl0bGUgPSBnbHVlOjpnbHVlKCJ7aWR9IHwge21ldGFkYXRhJGJpb2dlb317bWV0YWRhdGEkdW5pdH0gfCB7bWV0YWRhdGEkRVVOSVNhXzF9IHwge21ldGFkYXRhJEVVTklTYV8yfSB8IFF1YWxpdHk6IHttZXRhZGF0YSRxdWFsaXR5X2ZsYWd9IiksDQogICAgICB4ID0gIkRheSBvZiBZZWFyIiwNCiAgICAgIHkgPSAiSW5kZXggVmFsdWUiDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQp9KQ0KDQojIE5hbWUgdGhlIGxpc3QgYnkgUGxvdE9ic2VydmF0aW9uSUQNCm5hbWVzKHRzX3Bsb3RzX3EwKSA8LSB1bmlxdWVfaWRzMA0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnRzX3Bsb3RzX3EwWzFdDQpgYGANCg0KU2F2ZSBlYWNoIHBsb3QgdG8gYSBmaWxlOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kd2FsazIodHNfcGxvdHNfcTAsIHNlcV9hbG9uZyh0c19wbG90c19xMCksIH4gZ2dzYXZlKA0KICBmaWxlbmFtZSA9IHBhc3RlMCgib3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX3EwL3RzX3Bsb3RzX3EwIiwgLnksICIuanBlZyIpLA0KICBwbG90ID0gLngsDQogIHdpZHRoID0gOCwNCiAgaGVpZ2h0ID0gNQ0KKSkNCmBgYA0KDQojIFNtb290aCB0aGUgdGltZSBzZXJpZXMgb2YgTkRNSSBhbmQgTkRXSQ0KDQpVc2luZyBHQU0sIHdpdGhvdXQgcmVwbGFjaW5nIHZhbHVlcyBpbiBET1kgMeKAkzUwIGFuZCBET1kgMzE14oCTZW5kIHdpdGggc2VwYXJhdGUgYmFzZSB2YWx1ZXMsIGxhdGVyIHVzZSBvbmx5IHVud2VpZ2h0ZWQgR0FNLg0KDQpgYGB7cn0NCmNvbXB1dGVfdW53ZWlnaHRlZF9maXQgPC0gZnVuY3Rpb24oDQogICAgIyBEYXRhIGZyYW1lIGRmIHdpdGggaW5kZXggdmFsdWVzIG92ZXIgdGltZSAoRE9ZKQ0KICAgIGRmLCANCiAgICAjIE5hbWUgb2YgdGhlIHZlZ2V0YXRpb24gaW5kaWNlcyBjb2x1bW5zIChlLmcuLCAiTkRWSSIsICJFVkkiLCAiU0FWSSkNCiAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikNCikgew0KICAjIEluaXRpYWxpemUgbGlzdCB0byBzdG9yZSByZXN1bHRzDQogIGZpdHNfbGlzdCA8LSBsaXN0KCkNCiAgDQogICMgTG9vcCBvdmVyIGVhY2ggaW5kZXggY29sdW1uDQogIGZvciAoaW5kZXhfY29sIGluIGluZGV4X2NvbHMpIHsNCiAgICBkZl9pbmRleCA8LSBkZiAlPiUNCiAgICAgICMgUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIGluZGV4IHZhbHVlcyBhbmQgc29ydCBkYXRhIGJ5IERPWQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUgYXJyYW5nZShET1kpDQogICAgDQogICAgIyBFeHRyYWN0IHggKERPWSkgYW5kIHkgKGluZGV4KSB2ZWN0b3JzIGZvciBtb2RlbGxpbmcNCiAgICB4IDwtIGRmX2luZGV4JERPWQ0KICAgIHkgPC0gZGZfaW5kZXhbW2luZGV4X2NvbF1dDQogICAgDQogICAgIyBJZiB0aGVyZSBhcmUgZmV3ZXIgdGhhbiAxMSBvYnNlcnZhdGlvbnMgb3IgYWxsIHZhbHVlcyBhcmUgTkEsIHNraXANCiAgICBpZiAobGVuZ3RoKHgpIDwgMTEgfHwgYWxsKGlzLm5hKHkpKSkgew0KICAgICAgbmV4dA0KICAgIH0NCiAgICANCiAgICAjIEZpdCBHQU0gKHVud2VpZ2h0ZWQpIHdpdGggYSB0aGluIHBsYXRlIHNwbGluZSAoYnMgPSAidHAiKQ0KICAgICMgdG8gc21vb3RoIHRoZSBpbmRleCBjdXJ2ZQ0KICAgIGdhbV91bndlaWdodGVkIDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpKQ0KICAgIHByZWQgPC0gcHJlZGljdChnYW1fdW53ZWlnaHRlZCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgDQogICAgIyBDcmVhdGUgdGliYmxlIHRvIHN0b3JlIG9yaWdpbmFsIGFuZCBwcmVkaWN0ZWQgaW5kZXggdmFsdWVzDQogICAgZml0c19kZiA8LSB0aWJibGUoDQogICAgICBQbG90T2JzZXJ2YXRpb25JRCA9IHVuaXF1ZShkZiRQbG90T2JzZXJ2YXRpb25JRCksDQogICAgICBET1kgPSB4LA0KICAgICAgaW5kZXggPSBpbmRleF9jb2wsDQogICAgICB2YWx1ZSA9IHByZWQNCiAgICApDQogICAgDQogICAgZml0c19saXN0W1tpbmRleF9jb2xdXSA8LSBmaXRzX2RmDQogIH0NCiAgDQogIGlmIChsZW5ndGgoZml0c19saXN0KSA9PSAwKSB7DQogICAgcmV0dXJuKHRpYmJsZSgpKQ0KICB9DQogIA0KICBiaW5kX3Jvd3MoZml0c19saXN0KQ0KfQ0KYGBgDQoNCkFwcGx5IHRoZSBmdW5jdGlvbjoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxKQ0KDQojIEFwcGx5IHRoZSBmdW5jdGlvbiB0byBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQpleGVjdXRpb25fdGltZSA8LSBzeXN0ZW0udGltZSh7DQogIHdpdGhfcHJvZ3Jlc3Moew0KICAgIHNtb290aGVkX2RhdGEgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgZ3JvdXBfc3BsaXQoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICAgICAgc2V0X25hbWVzKG1hcF9jaHIoLiwgfiBhcy5jaGFyYWN0ZXIodW5pcXVlKC54JFBsb3RPYnNlcnZhdGlvbklEKSkpKSAlPiUNCiAgICAgIGZ1dHVyZV9tYXBfZGZyKH4gY29tcHV0ZV91bndlaWdodGVkX2ZpdChkZiA9IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikpLA0KICAgICAgICAgICAgICAgICAgICAgLnByb2dyZXNzID0gVFJVRSkNCiAgfSkNCn0pDQoNCnByaW50KGV4ZWN1dGlvbl90aW1lKQ0KYGBgDQoNCkxvb2s6DQoNCmBgYHtyfQ0Kc21vb3RoZWRfZGF0YQ0KYGBgDQoNClNhdmUgYXMgYW4gb2JqZWN0Og0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShzbW9vdGhlZF9kYXRhLCBmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQNCg0KYGBge3J9DQpzbW9vdGhlZF9kYXRhX2lkcyA8LSBzbW9vdGhlZF9kYXRhICU+JQ0KICAjIEpvaW4gdG8gZ2V0IGJpb2dlbyBhbmQgdW5pdA0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGJpb2dlbywgdW5pdCkgJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IEVVTklTIGluZm8NCiAgIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyKSkgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGFzLmNoYXJhY3RlcihQbG90T2JzZXJ2YXRpb25JRCkpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gdW5pcXVlKHNtb290aGVkX2RhdGFfaWRzJFBsb3RPYnNlcnZhdGlvbklEKQ0KDQojIENyZWF0ZSBhbmQgc3RvcmUgcGxvdHMgaW4gYSBsaXN0DQp0c19wbG90c19ORE1JX05EV0k8LSBtYXAodW5pcXVlX2lkcywgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIHNtb290aGVkX2RhdGFfaWRzICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKQ0KICANCiAgIyBFeHRyYWN0IG1ldGFkYXRhIGZvciB0aXRsZQ0KICBtZXRhZGF0YSA8LSBwbG90X2RhdGEgJT4lDQogICAgc2VsZWN0KGJpb2dlbywgdW5pdCwgRVVOSVNhXzEsIEVVTklTYV8yKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBET1ksIE5ETUksIE5EV0kpICU+JQ0KICAgICAgICAgICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5ETUksIE5EV0kpLCBuYW1lc190byA9ICJpbmRleCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogICAgICAgICAgICAgICAgICBmaWx0ZXIoUGxvdE9ic2VydmF0aW9uSUQgPT0gaWQpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRE9ZLCB5ID0gdmFsdWUpLCBhbHBoYSA9IDAuNikgKw0KICAgIGdlb21fbGluZShkYXRhID0gcGxvdF9kYXRhLCBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwNCiAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKGluZGV4KSkgKw0KICAgIGxhYnMoDQogICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntpZH0gfCB7bWV0YWRhdGEkYmlvZ2VvfXttZXRhZGF0YSR1bml0fSB8IHttZXRhZGF0YSRFVU5JU2FfMX0gfCB7bWV0YWRhdGEkRVVOSVNhXzJ9IiksDQogICAgICB4ID0gIkRheSBvZiBZZWFyIiwNCiAgICAgIHkgPSAiSW5kZXggVmFsdWUiDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQp9KQ0KDQojIE5hbWUgdGhlIGxpc3QgYnkgUGxvdE9ic2VydmF0aW9uSUQNCm5hbWVzKHRzX3Bsb3RzX05ETUlfTkRXSSkgPC0gdW5pcXVlX2lkcw0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHRzX3Bsb3RzX05ETUlfTkRXSVtbMV1dKQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX05ETUlfTkRXSSwgc2VxX2Fsb25nKHRzX3Bsb3RzX05ETUlfTkRXSSksIH4gZ2dzYXZlKA0KICBmaWxlbmFtZSA9IHBhc3RlMCgib3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX05ETUlfTkRXSS90c19wbG90c19ORFZJX05ETUkiLA0KICAgICAgICAgICAgICAgICAgICAueSwgIi5qcGVnIiksDQogIHBsb3QgPSAueCwNCiAgd2lkdGggPSA4LA0KICBoZWlnaHQgPSA1DQopKQ0KYGBgDQoNCiMgR2V0IGluZGljZXMgZGF0YSAobWF4LiBhbmQgbWluLikNCg0KQ2FyZWZ1bCEgVGhlc2UgbWF4aW11bSBhbmQgbWluaW11bSB2YWx1ZXMgYXJlIGZyb20gdGhlIHNtb290aGVkIHRpbWUgc2VyaWVzLiBGb3IgTkRWSSAvIEVWSSAvIFNBVkkgdmFsdWVzIGluIERPWSAx4oCTNTAgYW5kIERPWSAzMTXigJNlbmQsIHJlbWVtYmVyIHRoYXQgdGhlIEdBTSBzbW9vdGhpbmcgZnVuY3Rpb24gcmVwbGFjZWQgdGhlIG9yaWdpbmFsIHZhbHVlcyB3aXRoIHRoZSBtZWFuIGJhc2UgdmFsdWUgb2Ygb2JzZXJ2YXRpb25zIGR1cmluZyBlYWNoIG9mIHRoZXNlIHJlc3BlY3RpdmUgcGVyaW9kcy4gVGhpcyB3YXMgc28gZmFyIG5vdCBkb25lIGZvciBORE1JIGFuZCBORFdJLiANCg0KYGBge3J9DQpmaW5hbF9pbmRpY2VzX2RhdGEgPC0gR0FNX2RhdGEgJT4lDQogIGdyb3VwX2J5KFBsb3RPYnNlcnZhdGlvbklELCBpbmRleCkgJT4lDQogIHN1bW1hcmlzZShtYXggPSBtYXgodmFsdWUpLCBtaW4gPSBtaW4odmFsdWUpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhtYXgsIG1pbiksDQogICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpICU+JQ0KICBmdWxsX2pvaW4oDQogICAgc21vb3RoZWRfZGF0YSAlPiUNCiAgICAgIGdyb3VwX2J5KFBsb3RPYnNlcnZhdGlvbklELCBpbmRleCkgJT4lDQogICAgICBzdW1tYXJpc2UobWF4ID0gbWF4KHZhbHVlKSwgbWluID0gbWluKHZhbHVlKSkgJT4lDQogICAgICB1bmdyb3VwKCkgJT4lDQogICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhtYXgsIG1pbiksDQogICAgICAgICAgICAgICAgICBuYW1lc19nbHVlID0gIntpbmRleH1fey52YWx1ZX0iKQ0KICAgICkNCmBgYA0KDQojIEdldCBwaGVub2xvZ3kgZGF0YQ0KDQpVc2UgR0FNIGl0ZXJfMyB0byBnZXQgZGF0ZXMgb2YgdGhlIG1vbWVudHMsIHZhbHVlcyBhdCB0aG9zZSBtb21lbnRzIGFuZCBBVUMgKHRpbWUtaW50ZWdyYXRlZCBpbmRpY2VzKSBiZXR3ZWVuIFNPUyBhbmQgRU9TOg0KDQpgYGB7cn0NCiMgSm9pbiB0byBnZXQgdmFsdWVzIGF0IFNPUywgUE9TLCBFT1MgYW5kIGF1Yw0KZmluYWxfcGhlbm9sb2d5X2RhdGEgPC0gR0FNX2RhdGEgJT4lDQogIG11dGF0ZSgNCiAgICBzdGFnZSA9IGNhc2Vfd2hlbigNCiAgICAgIERPWSA9PSBzb3Nfc2xvcGUgfiAic29zX3Nsb3BlIiwNCiAgICAgIERPWSA9PSBzb3NfdGhyZXNob2xkIH4gInNvc190cmVzaG9sZCIsDQogICAgICBET1kgPT0gcG9zIH4gInBvcyIsDQogICAgICBET1kgPT0gZW9zX3Nsb3BlIH4gImVvc19zbG9wZSIsDQogICAgICBET1kgPT0gZW9zX3RocmVzaG9sZCB+ICJlb3NfdGhyZXNob2xkIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShzdGFnZSkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBzdGFnZSwgZG95ID0gRE9ZLCB2YWx1ZSkgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBjKGluZGV4LCBzdGFnZSksDQogICAgdmFsdWVzX2Zyb20gPSBjKGRveSwgdmFsdWUpLA0KICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97c3RhZ2V9X3sudmFsdWV9Ig0KICApICU+JQ0KICAjIENvbnZlcnQgbGlzdCBjb2xzIHRvIHJlZ3VsYXIgbnVtZXJpYyBjb2xzDQogIG11dGF0ZSgNCiAgICBORFZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoTkRWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIE5EVklfc29zX3RyZXNob2xkX3ZhbHVlID0gbWFwX2RibChORFZJX3Nvc190cmVzaG9sZF92YWx1ZSwgMSksDQogICAgTkRWSV9wb3NfdmFsdWUgPSBtYXBfZGJsKE5EVklfcG9zX3ZhbHVlLCAxKSwNCiAgICBORFZJX2Vvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoTkRWSV9lb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIE5EVklfZW9zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoTkRWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBFVklfc29zX3Nsb3BlX3ZhbHVlID0gbWFwX2RibChFVklfc29zX3Nsb3BlX3ZhbHVlLCAxKSwNCiAgICBFVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoRVZJX3Nvc190cmVzaG9sZF92YWx1ZSwgMSksDQogICAgRVZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoRVZJX3Bvc192YWx1ZSwgMSksDQogICAgRVZJX2Vvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoRVZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgRVZJX2Vvc190aHJlc2hvbGRfdmFsdWUgPSBtYXBfZGJsKEVWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBTQVZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIFNBVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3NfdHJlc2hvbGRfdmFsdWUsIDEpLA0KICAgIFNBVklfcG9zX3ZhbHVlID0gbWFwX2RibChTQVZJX3Bvc192YWx1ZSwgMSksDQogICAgU0FWSV9lb3Nfc2xvcGVfdmFsdWUgPSBtYXBfZGJsKFNBVklfZW9zX3Nsb3BlX3ZhbHVlLCAxKSwNCiAgICBTQVZJX2Vvc190aHJlc2hvbGRfdmFsdWUgPSBtYXBfZGJsKFNBVklfZW9zX3RocmVzaG9sZF92YWx1ZSwgMSkNCiAgKSAlPiUNCiAgZnVsbF9qb2luKEdBTV9kYXRhICU+JQ0KICAgICAgICAgICAgICBkaXN0aW5jdChQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIGF1Y19zbG9wZSwgYXVjX3RocmVzaG9sZCkgJT4lDQogICAgICAgICAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKGF1Y19zbG9wZSwgYXVjX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpKQ0KYGBgDQoNCiMgSm9pbiBpbmRpY2VzIGFuZCBwaGVub2xvZ3kgZGF0YQ0KDQpgYGB7cn0NCmZpbmFsX1JTX2RhdGEgPC0gZnVsbF9qb2luKA0KICAjIEluZGljZXMgZGF0YSAobWF4IGFuZCBtaW4pDQogIGZpbmFsX2luZGljZXNfZGF0YSwNCiAgIyBBdmVyYWdlIHZhbHVlcyBvZiBpbmRpY2VzIHBlciBtb250aA0KICBtb250aGx5X2F2Z19pbmRpY2VzICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKGF2Z192YWx1ZV8wMTphdmdfdmFsdWVfMTIpLA0KICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpDQogICkgJT4lDQogIGZ1bGxfam9pbigNCiAgICAjIFBoZW5vbG9neSBkYXRhDQogICAgZmluYWxfcGhlbm9sb2d5X2RhdGEgDQogICAgKSAlPiUNCiAgIyBTb3J0IGNvbHMgaW4gYWxwaGFiZXRpY2FsIG9yZGVyDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgc29ydChuYW1lcyguKVtuYW1lcyguKSAhPSAiUGxvdE9ic2VydmF0aW9uSUQiXSkpDQpgYGANCg0KIyBBZGQgRVVOSVMgY29kZXMNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzIDwtIGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgbGVmdF9qb2luKGRiX0V1cm9wYV9hbGxvYnMpDQpgYGANCg0KIyBIRVJFOiBSRVZJU0U6IE1vbnRobHkgc3BlY3Ryb3BoZW5vbG9neSBwZXIgaGFiaXRhdCB0eXBlDQoNCmBgYHtyfQ0KIyBQcmVwYXJlIHRoZSBkYXRhDQpkYXRhX21vbnRobHlfRVVOSVNhXzEgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBtdXRhdGUobW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1vbnRoLCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9ORFZJID0gbWVhbihORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIHNkX05EVkkgPSBzZChORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fTkRWSSA9IHN1bSghaXMubmEoTkRWSSkpLA0KICAgIG1lYW5fRVZJID0gbWVhbihFVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfRVZJID0gc2QoRVZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fRVZJID0gc3VtKCFpcy5uYShFVkkpKSwNCiAgICBtZWFuX1NBVkkgPSBtZWFuKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfU0FWSSA9IHNkKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9TQVZJID0gc3VtKCFpcy5uYShTQVZJKSksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApDQpkYXRhX21vbnRobHlfRVVOSVNhXzIgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBtdXRhdGUobW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1vbnRoLCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtZWFuX05EVkkgPSBtZWFuKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfTkRWSSA9IHNkKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9ORFZJID0gc3VtKCFpcy5uYShORFZJKSksDQogICAgbWVhbl9FVkkgPSBtZWFuKEVWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9FVkkgPSBzZChFVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9FVkkgPSBzdW0oIWlzLm5hKEVWSSkpLA0KICAgIG1lYW5fU0FWSSA9IG1lYW4oU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9TQVZJID0gc2QoU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBuX1NBVkkgPSBzdW0oIWlzLm5hKFNBVkkpKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkNCmBgYA0KDQpgYGB7cn0NCiMgUGxvdHMNCg0KIyBFVU5JU2FfMQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fTkRWSSwgY29sb3IgPSBFVU5JU2FfMSwgZ3JvdXAgPSBFVU5JU2FfMSkpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fTkRWSSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fTkRWSSAtIHNkX05EVkksIHltYXggPSBtZWFuX05EVkkgKyBzZF9ORFZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBORFZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJORFZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JU2FfMSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fRVZJLCBjb2xvciA9IEVVTklTYV8xLCBncm91cCA9IEVVTklTYV8xKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaXplID0gbl9FVkkpKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVNhXzEpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzEsIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX1NBVkksIGNvbG9yID0gRVVOSVNhXzEsIGdyb3VwID0gRVVOSVNhXzEpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBuX1NBVkkpKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX1NBVkkgLSBzZF9TQVZJLCB5bWF4ID0gbWVhbl9TQVZJICsgc2RfU0FWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgU0FWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiU0FWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVNhXzEpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgRVVOSVNhXzINCg0KIyBORFZJDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBuX05EVkkpKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVNhXzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUiIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fTkRWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fTkRWSSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fTkRWSSAtIHNkX05EVkksIHltYXggPSBtZWFuX05EVkkgKyBzZF9ORFZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBORFZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJORFZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JU2FfMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJTIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9ORFZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaXplID0gbl9ORFZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9ORFZJIC0gc2RfTkRWSSwgeW1heCA9IG1lYW5fTkRWSSArIHNkX05EVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IE5EVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIk5EVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBuX05EVkkpKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVNhXzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgRVZJDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fRVZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlIiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fRVZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlMiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fRVZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fRVZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIFNBVkkNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiUSIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fU0FWSSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JU2FfMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgZmlsdGVyKEVVTklTYV8xID09ICJSIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9TQVZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaXplID0gbl9TQVZJKSkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9TQVZJIC0gc2RfU0FWSSwgeW1heCA9IG1lYW5fU0FWSSArIHNkX1NBVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IFNBVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIlNBVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTYV8yKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSBmaWx0ZXIoRVVOSVNhXzEgPT0gIlMiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX1NBVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBuX1NBVkkpKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX1NBVkkgLSBzZF9TQVZJLCB5bWF4ID0gbWVhbl9TQVZJICsgc2RfU0FWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgU0FWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiU0FWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVNhXzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIGZpbHRlcihFVU5JU2FfMSA9PSAiVCIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IG5fU0FWSSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JU2FfMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQojIE1PRElGWTogQ2FsY3VsYXRlIG90aGVyIHBoZW5vbG9naWNhbCBtZXRyaWNzDQoNCmBgYHtyfQ0KZmluYWxfUlNfZGF0YSA8LSBmaW5hbF9SU19kYXRhICU+JQ0KICBtdXRhdGUoDQogICAgIyBHcm93aW5nIHNlYXNvbiBkdXJhdGlvbg0KICAgIE5EVklfZ3NkID0gTkRWSV9lb3NfZG95IC0gTkRWSV9zb3NfZG95LA0KICAgIEVWSV9nc2QgPSBORFZJX2Vvc19kb3kgLSBORFZJX3Nvc19kb3ksDQogICAgU0FWSV9nc2QgPSBTQVZJX2Vvc19kb3kgLSBTQVZJX3Nvc19kb3ksDQogICAgIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBzb3MNCiAgICBORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSA9IE5EVklfcG9zX3ZhbHVlIC0gTkRWSV9zb3NfdmFsdWUsDQogICAgRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSA9IEVWSV9wb3NfdmFsdWUgLSBFVklfc29zX3ZhbHVlLA0KICAgIFNBVklfZGlmZl9wb3Nfc29zX3ZhbHVlID0gU0FWSV9wb3NfdmFsdWUgLSBTQVZJX3Nvc192YWx1ZSwNCiAgICAjIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIGVvcw0KICAgIE5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlID0gTkRWSV9wb3NfdmFsdWUgLSBORFZJX2Vvc192YWx1ZSwNCiAgICBFVklfZGlmZl9wb3NfZW9zX3ZhbHVlID0gRVZJX3Bvc192YWx1ZSAtIEVWSV9lb3NfdmFsdWUsDQogICAgU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUgPSBTQVZJX3Bvc192YWx1ZSAtIFNBVklfZW9zX3ZhbHVlLA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX2RveSA9IE5EVklfcG9zX2RveSAtIE5EVklfc29zX2RveSwNCiAgICBFVklfZGlmZl9wb3Nfc29zX2RveSA9IEVWSV9wb3NfZG95IC0gRVZJX3Nvc19kb3ksDQogICAgU0FWSV9kaWZmX3Bvc19zb3NfZG95ID0gU0FWSV9wb3NfZG95IC0gU0FWSV9zb3NfZG95LA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBlb3MgYW5kIHBvcw0KICAgIE5EVklfZGlmZl9lb3NfcG9zX2RveSA9IE5EVklfZW9zX2RveSAtIE5EVklfcG9zX2RveSwNCiAgICBFVklfZGlmZl9lb3NfcG9zX2RveSA9IEVWSV9lb3NfZG95IC0gRVZJX3Bvc19kb3ksDQogICAgU0FWSV9kaWZmX2Vvc19wb3NfZG95ID0gU0FWSV9lb3NfZG95IC0gU0FWSV9wb3NfZG95DQogICkNCmBgYA0KDQojIyBDaGVja3MNCg0KYGBge3J9DQojIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JSANCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZ3NkIDw9IDAgfCBFVklfZ3NkIDw9IDAgfCBTQVZJX2dzZCA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUgPD0gMCkpDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3Nfc29zX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUgPD0gMCkpDQojIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIGVvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX2Vvc192YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFNBVklfZGlmZl9wb3NfZW9zX3ZhbHVlIDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfZG95IDw9IDAgfCBFVklfZGlmZl9wb3Nfc29zX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9kaWZmX3Bvc19zb3NfZG95IDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIGVvcyBhbmQgcG9zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX2Vvc19wb3NfZG95IDw9IDAgfCBFVklfZGlmZl9lb3NfcG9zX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9kaWZmX2Vvc19wb3NfZG95IDw9IDApKQ0KYGBgDQoNCiMgSEVSRTogUnVuLiBEZXRlY3QgbnVtYmVyIG9mIHBlYWtzIGluIHNtb290aGVkIGN1cnZlcw0KDQpgYGB7cn0NCiMgU2V0IHVwIHBhcmFsbGVsIHBsYW4NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gbWluKHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMSkpDQoNCiMgRGVmaW5lIHBlYWstY291bnRpbmcgZnVuY3Rpb24NCmNvdW50X3BlYWtzIDwtIGZ1bmN0aW9uKGRmKSB7DQogICMgQ29udmVydCAxRCBhcnJheSBjb2x1bW4gdG8gbnVtZXJpYyB2ZWN0b3INCiAgeSA8LSBhcy5udW1lcmljKGRmJHZhbHVlKQ0KICANCiAgcGVha3MgPC0gZmluZHBlYWtzKHksIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCB0aHJlc2hvbGQgPSAwLjAyKQ0KICANCiAgdGliYmxlKA0KICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmJFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICBpbmRleCA9IHVuaXF1ZShkZiRpbmRleCksDQogICAgbnVtX3BlYWtzID0gaWYgKCFpcy5udWxsKHBlYWtzKSkgbnJvdyhwZWFrcykgZWxzZSAwDQogICkNCn0NCg0KIyBBcHBseSBwZWFrIGNvdW50aW5nIGluIHBhcmFsbGVsDQpwZWFrX2NvdW50cyA8LSBHQU1fZGF0YSAlPiUNCiAgZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiAgYXJyYW5nZShET1kpICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgpICU+JQ0KICBuZXN0KCkgJT4lDQogIG11dGF0ZShyZXN1bHQgPSBmdXR1cmVfbWFwKGRhdGEsIGNvdW50X3BlYWtzLCAucHJvZ3Jlc3MgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3B0aW9ucyA9IGZ1cnJyX29wdGlvbnMoc2NoZWR1bGluZyA9IEluZikpKSAlPiUNCiAgc2VsZWN0KC1kYXRhKSAlPiUNCiAgdW5uZXN0KHJlc3VsdCkNCg0KIyBTdW1tYXJpemUgcmVzdWx0DQpwZWFrX2NvdW50c19zdW1tYXJ5IDwtIHBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKQ0KYGBgDQoNCiMgSEVSRTogc2F2ZQ0KDQpgYGB7cn0NCiMgIyBGdW5jdGlvbiB0byBjb3VudCBwZWFrcyBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRA0KIyBjb3VudF9wZWFrcyA8LSBmdW5jdGlvbihkZikgew0KIyAgIHkgPC0gZGYkdmFsdWUNCiMgICBwZWFrcyA8LSBmaW5kcGVha3MoeSwgDQojICAgICAgICAgICAgICAgICAgICAgICMgTWluaW11bSBudW1iZXIgb2YgaW5kaWNlcyAoZS5nLiwgRE9ZIHN0ZXBzKQ0KIyAgICAgICAgICAgICAgICAgICAgICAjIGJldHdlZW4gdHdvIHBlYWtzDQojICAgICAgICAgICAgICAgICAgICAgIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCANCiMgICAgICAgICAgICAgICAgICAgICAgIyBNaW5pbXVtIHZlcnRpY2FsIGRpZmZlcmVuY2UgYmV0d2VlbiBhIHBlYWsNCiMgICAgICAgICAgICAgICAgICAgICAgIyBhbmQgaXRzIHN1cnJvdW5kaW5nIHZhbHVlDQojICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDIpDQojICAgbnVtX3BlYWtzIDwtIGlmICghaXMubnVsbChwZWFrcykpIG5yb3cocGVha3MpIGVsc2UgMA0KIyAgIHJldHVybih0aWJibGUoUGxvdE9ic2VydmF0aW9uSUQgPSB1bmlxdWUoZGYkUGxvdE9ic2VydmF0aW9uSUQpLA0KIyAgICAgICAgICAgICAgICAgbnVtX3BlYWtzID0gbnVtX3BlYWtzKSkNCiMgfQ0KIyANCiMgIyBBcHBseSB0byBlYWNoIGdyb3VwDQojIHBlYWtfY291bnRzIDwtIEdBTV9kYXRhICU+JQ0KIyAgIG11dGF0ZSh2YWx1ZSA9IG1hcF9kYmwodmFsdWUsIDEpKSAlPiUNCiMgICBkcGx5cjo6ZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiMgICBhcnJhbmdlKERPWSkgJT4lDQojICAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiMgICBncm91cF9tb2RpZnkofiBjb3VudF9wZWFrcygueCkpICU+JQ0KIyAgIHVuZ3JvdXAoKQ0KIyANCiMgIyBWaWV3IHJlc3VsdA0KIyBwZWFrX2NvdW50cyAlPiUgY291bnQoaW5kZXgsIG51bV9wZWFrcykNCmBgYA0KDQojIyBQbG90IG51bWJlciBvZiBwZWFrcw0KDQpgYGB7cn0NCnBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaW5kZXgsIHkgPSBuLCBmaWxsID0gZmFjdG9yKG51bV9wZWFrcykpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpDQpgYGANCg0KRVZJIGdpdmVzIGxlc3MgcHJvYmxlbXMsIG1heWJlIHVzZSBvbmx5IHRoaXMgb25lPw0KDQojIyBBZGQgbnVtYmVyIG9mIHBlYWtzIHRvIGRhdGENCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbihwZWFrX2NvdW50cyAlPiUNCiAgICAgICAgICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGluZGV4LCB2YWx1ZXNfZnJvbSA9IG51bV9wZWFrcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZ2x1ZSA9ICJ7aW5kZXh9X3sudmFsdWV9IikpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIFBsb3RPYnNlcnZhdGlvbklEcyB3aXRoIHplcm8gcGVha3MNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfbnVtX3BlYWtzID09IDApICU+JQ0KICBwdWxsKFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgYXMuY2hhcmFjdGVyKCkNCg0KIyBDcmVhdGUgYW5kIHN0b3JlIHBsb3RzIGluIGEgbGlzdA0KcGxvdHNfRVZJXzBwZWFrcyA8LSBtYXAodW5pcXVlX2lkc1sxOjUwXSwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoYXMuY2hhcmFjdGVyKFBsb3RPYnNlcnZhdGlvbklEKSA9PSBpZCkgJT4lDQogICAgZHBseXI6OmZpbHRlcihmaXRfdHlwZSA9PSAib2JzZXJ2ZWQiIHwgZml0X3R5cGUgPT0gIml0ZXJfMyIpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIm9ic2VydmVkIiksIA0KICAgICAgICAgICAgICAgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIGFscGhhID0gMC41KSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IGRwbHlyOjpmaWx0ZXIocGxvdF9kYXRhLCBmaXRfdHlwZSA9PSAiaXRlcl8zIiksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvcyksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gZHBseXI6OmZpbHRlcihwbG90X2RhdGEsIGZpdF90eXBlID09ICJpdGVyXzMiKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gcG9zKSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRvdHRlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBlb3MpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgiUGxvdE9ic2VydmF0aW9uSUQ6IHtpZH0iKSwNCiAgICAgIHggPSAiRGF5IG9mIFllYXIiLA0KICAgICAgeSA9ICJJbmRleCBWYWx1ZSINCiAgICApICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCn0pDQoNCiMgTmFtZSB0aGUgbGlzdCBieSBQbG90T2JzZXJ2YXRpb25JRA0KbmFtZXMocGxvdHNfRVZJXzBwZWFrcykgPC0gdW5pcXVlX2lkc1sxOjUwXQ0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHBsb3RzX0VWSV8wcGVha3NbWzFdXSkNCmBgYA0KDQojIyBGdXJ0aGVyIGNoZWNrcyAoRVZJKQ0KDQpgYGB7cn0NCiMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSA8PSAwKSkNCmZpbmFsX1JTX2RhdGEgJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSA8PSAwKSAlPiUNCiAgY291bnQoRVZJX251bV9wZWFrcykNCiMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgZW9zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX2Vvc192YWx1ZSA8PSAwKSkNCmZpbmFsX1JTX2RhdGEgJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX2Vvc192YWx1ZSA8PSAwKSAlPiUNCiAgY291bnQoRVZJX251bV9wZWFrcykNCmBgYA0KDQojIEFkZCBzb21lIGNvbHVtbnMgbmVlZGVkDQoNCmBgYHtyfQ0KZmluYWxfUlNfZGF0YSA8LSBmaW5hbF9SU19kYXRhICU+JQ0KICBsZWZ0X2pvaW4oDQogICAgZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgZGlzdGluY3QoUGxvdE9ic2VydmF0aW9uSUQsIHllYXIsIGJpb2dlbywgdW5pdCwgTGN0bm10aCkNCiAgICApDQpgYGANCg0KIyBBZGQgY2Fub3B5IGhlaWdodCBkYXRhDQoNClJlYWQgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KZGF0YV9SU19DSCA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvTU9USVZBVEVfUlNfZGF0YS9DYW5vcHlfSGVpZ2h0XzFtL0V1cm9wZV9wb2ludHNfQ2Fub3B5SGVpZ2h0XzFtLmNzdiIpDQpkYl9FdXJvcGEgPC0gcmVhZF9jc3YoDQogIGhlcmUoIi4uIiwgIkRCX2ZpcnN0X2NoZWNrIiwgImRhdGEiLCAiY2xlYW4iLCJkYl9FdXJvcGFfMjAyNTAxMDcuY3N2IikNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YV9SU19DSF9JRCA8LSBkYl9FdXJvcGEgJT4lDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgb2JzX3VuaXF1ZV9pZCkgJT4lDQogIHJpZ2h0X2pvaW4oZGF0YV9SU19DSCAlPiUNCiAgICAgICAgICAgICAgIyBSZW5hbWUgdG8gYmUgYWJsZSB0byBqb2luIG9uIHRoaXMgY29sdW1uDQogICAgICAgICAgICAgIHJlbmFtZShvYnNfdW5pcXVlX2lkID0gb2JzX3VuaXF1ZSkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGNhbm9weV9oZWlnaHQpDQpgYGANCg0KSm9pbjoNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbihkYXRhX1JTX0NIX0lEICU+JQ0KICAgICAgICAgICAgICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBmYWN0b3IoUGxvdE9ic2VydmF0aW9uSUQpKSkNCmBgYA0KDQojIFNhdmUgdG8gY2xlYW4gZGF0YQ0KDQpgYGB7cn0NCndyaXRlX3RzdihmaW5hbF9SU19kYXRhLA0KICAgICAgICAgIGhlcmUoImRhdGEiLCAiY2xlYW4iLCJmaW5hbF9SU19kYXRhX2JhbmRzX1MyX2FsbC5jc3YiKSkNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=